О чём эта статья?
В этой статье я расскажу, как с помощью HTML и CSS (на этот раз без JS) можно легко стилизовать радиокнопки (radioboxes) и флажки (checkboxes) на своём сайте.
Исходные данные
Эта статья продолжает цикл "Как стилизовать нестилизуемое" :). Ранее мы говорили о том, как поменять внешний вид полос прокрутки с помощью плагина jScrollPane и несложных стилей CSS, а также о том, как стилизовать поле для загрузки файла, используя CSS, правильную разметку и несколько строк кода JS. На очереди радиокнопки и флажки. В этой статье в качестве примера я буду стилизовать радиокнопки, но случай с флажками абсолютно аналогичен.
Итак, что мы имеем? Пусть на нашем сайте располагается некая форма, в которой, кроме всего прочего, имеется несколько радиокнопок. Ну, например, с их помощью пользователь выбирает, какой язык программирования ему нравится больше всего. Разметка будет примитивна до неприличия:
<form>
<div class="option-group">
<input type="radio" value="ruby" name="prog_lang" id="ruby" />
<label for="ruby">Ruby</label>
</div>
<div class="option-group">
<input type="radio" value="Python" name="prog_lang" id="Python" />
<label for="Python">Python</label>
</div>
<div class="option-group">
<input type="radio" value="Brainfuck" name="prog_lang" id="Brainfuck" />
<label for="Brainfuck">Brainfuck</label>
</div>
</form>
Все элементы и атрибуты, не относящиеся к делу, я опустил. Каждую пару лейбл-радиокнопка я сразу обрамил в блочный тэг .option-group - так радиокнопки не будут отображаться на одной строке, да и при стилизации нам это поможет. Кроме того, все радиокнопки должны иметь одинаковое значение атрибута name, иначе они будут принадлежать к разным группам и пользователь сможет выбрать сразу несколько элементов (тогда как радиокнопки подразумевают, что выбрать можно что-то одно; в противном случае, мы бы использовали флажки). И последнее: для каждого лейбла я задал атрибут for, значение которого совпадает с атрибутом id той радиокнопки, которой принадлежит лейбл. Это сделано для того, чтобы при клике по лейблу сразу менялось значение соответствующей радиокнопки - так удобнее для пользователя. Получилась вот такая нехитрая форма:
План работы
Теперь предположим, что наш сайт выполнен в тёмных тонах и все радиокнопки должны в него органично вписываться - дизайнер уже прислал нам требуемый внешний вид. Как нам перекрасить эти элементы в нужный цвет? К сожалению, простыми инструкциями CSS типа background-color тут ничего не добьёшься, поэтому придётся применять другой способ. Из макета, присланного дизайнером, мы вырежем внешний вид радиокнопок в двух состояниях - обычном и нажатом (могут быть и другие, но ограничимся этими). Затем мы скроем наши радиокнопки со стандартным дизайном, но оставим их на том же самом месте, чтобы пользователь мог кликать по ним. Далее просто подложим в качестве фонового рисунка под каждую скрытую радиокнопку то изображение, которое мы только что вырезали из макета; в зависимости от состояния кнопки мы будем менять фоновый рисунок.
Вроде бы, ничего сложного, но здесь возникает два вопроса. Первый: для какого элемента фоновое изображение задавать? Вряд ли у нас получится сделать это для самой радиокнопки! Второй: как нам объяснить CSS, в каком случае менять фоновые изображения?

Мы скрыли радиокнопку, поместили под неё лейбл и задали фоновый рисунок, но не сделали для лейбла отступ слева.
С первым вопросом всё достаточно просто. Действительно, для самой радиокнопки задать фоновый рисунок не получится, но ведь у нас есть лейблы! Требуется просто сделать для каждого лейбла отступ слева достаточный для того, чтобы туда поместилась наша "поддельная" (то есть нарисованная) радиокнопка, и наложить фоновый рисунок таким образом, чтобы он располагался слева и не дублировался по всей длине лейбла. Здесь придётся немного похимичить с позиционированием элементов (в частности, сделать так, чтобы лейбл помещался непосредственно под настоящей радиокнопкой), но, в принципе, ничего сложного в этом нет.

А теперь мы добавили отступ слева (он закрашен фиолетовым; сам лейбл - голубым) так, чтобы текст не наползал на фон.
Посмотрите на рисунки, чтобы понять, как это будет выглядеть. Здесь я специально оставил белый цвет фона, чтобы фоновый рисунок лейблов чётко выделялся. На первом рисунке видно, что радиокнопка скрыта, фоновый рисунок наложен, но отступа слева нет и из-за этого текст лейбла наползает на него. На втором рисунке эта проблема устранена и создаётся ощущение, что это не фоновый рисунок, а настоящая радиокнопка.
Теперь ко второму вопросу. Для того, чтобы написать стиль CSS, который указывает только на ту радиокнопку, которая была помечена пользователем, мы используем псевдоселектор :checked (который также работает и для флажков), таким образом, запись input[type='radio']:checked означает: выбрать только ту радиокнопку, которая помечена. Однако нам требуется не сама радиокнопка, а соответствующий ей лейбл, ведь фоновый рисунок мы задаём для него! Для этого используем селектор CSS + вот таким образом: input[type='radio']:checked + label. Эта запись означает, что необходимо выбрать лейбл, который находится непосредственно после отмеченной пользователем радиокнопки, причём лейбл должен быть на том же уровне вложенности. Так мы решим поставленную задачу. Приступим к реализации.
За работу!
В присланном макете радиокнопки имеют ширину и высоту 17px, цвет фона страницы - #363636. Также зададим небольшие отступы для страницы, чтобы форма не лепилась к самому краю. Вот, что получится:
body {
padding: 10px;
background-color: #363636;
}
input[type='radio'] {
opacity: 0;
filter: alpha(opacity=0);
width: 17px;
height: 17px;
z-index: 99999;
position: relative;
cursor: pointer;
}
input[type='radio'] + label {
padding-left: 24px;
background: url('https://s3-eu-west-1.amazonaws.com/radiantwind/js_css/radio_sprite.png') transparent no-repeat 0 -18px;
position: relative;
left: -21px;
font-size: 16px;
color: #efefef;
cursor: pointer;
height: 17px;
}
input[type='radio']:checked + label {
background-position: 0 0;
}
По большей части, тут нет почти никаких неожиданностей. С помощью opacity: 0; мы скрываем радиокнопки, затем задаём нужную ширину и обязательно z-index, чтобы радиокнопка находилась поверх лейбла и на неё можно было кликнуть (хотя она и невидима). Надо также помнить про position: relative;, иначе z-index не будет работать. Далее стилизуем лейблы. Обязательно задаём padding-left так, чтобы хватило места для фонового рисунка (который будет изображать радиокнопку) и чтобы текст лейбла не располагался слишком близко - для вашего случая эти значения, конечно, могут быть другими. Далее мы также относительно позиционируем лейблы, сдвинем их влево с помощью left так, чтобы они находились под невидимыми радиокнопками, после чего меняем цвета и размер шрифта. Вроде бы всё, но что за стиль для input[type='radio']:checked + label? Что это за background-position?
В данном случае мы используем так называемый спрайт (sprite) для фонового рисунка. Мы могли бы сделать по-простому - указать для всех лейблов один фоновый рисунок, а для того лейбла, который идёт после помеченной радиокнопки, другой, но в этом случае пользователи бы замечали небольшой лаг после клика по радиокнопке. Проявлялся бы он очень просто: пользователь кликнул по радиокнопке, а её визуальное состояние изменилось бы через полсекунды (плюс-минус). Это происходит из-за того, что при первом клике браузер сначала загружает тот фоновый рисунок, который установлен для нажатого состояния, а только потом его отображает (само собой, на загрузку этого рисунка требуется время, пусть и небольшое). Поэтому сейчас на многих сайтах изображения объединяют в спрайты. Спрайт представляет собой большой рисунок, который состоит из нескольких изображений. Браузер загружает его сразу, а затем отображает для фона разных элементов разные его части. Откройте вот эту ссылку - вы увидите спрайт, который мы используем в данном случае. Он состоит из двух изображений: нажатой радиокнопки и ненажатой. Его высота составляет 34px (очевидно, что высота каждого отдельного изображения равна 17px). Этот спрайт можно представить как ленту, которую мы можем пролистывать вверх и вниз и показывать нужную часть в "окошке" нужного размера. Окошком будет выступать наш лейбл, высота которого составляет 17px, то есть в него поместится ровно одно изображение из спрайта - нам достаточно установить "ленту" в нужное положение. См. рисунок, который иллюстрирует написанное.

Синим помечен фоновый рисунок (спрайт), который мы двигаем вверх и вниз с помощью background-position; красным - элемент фиксированного размера, для которого задан фоновый рисунок.
Так вот инструкция background: url('https://s3-eu-west-1.amazonaws.com/radiantwind/js_css/radio_sprite.png') transparent no-repeat 0 -18px; говорит о том, что в качестве фоного рисунка следует использовать наш спрайт, при этом цвет фона должен быть прозрачным, спрайт не должен замостить наш блок, если его ширины не хватит, чтобы покрыть его полностью, а также то, что выводить изображение следует с точки 0px; 0px, то есть с самого верха (счёт идёт от левого верхнего края; изначально браузер пытается выводить самую нижнюю часть изображения, поэтому нам требуется сдвинуться вверх на 18px). 0 -18px - это координаты по x и по y. Так как наша лента разворачивается в высоту, мы задаём только смещение по y, не трогая x. Посмотрите на наш спрайт ещё раз и всё встанет на свои места: в верхней части находится как раз рисунок радиокнопки в обычном состоянии, а под ней - в нажатом. Именно поэтому далее идёт инструкция background-position: 0 0;, которая выводит для нажатой радиокнопки другой фон, из другой части спрайта. Мы могли бы полностью скопировать инструкцию background, приведённую выше, и изменить в ней координаты смещения, но в этом нет необходимости - запись можно упростить.
В Интернете существуют онлайн-сервисы, которые преобразуют загруженные изображения в один спрайт и даже создают правильные стили (чтобы не пришлось подбирать смещение вручную). А для Ruby on Rails есть библиотека compass-rails, которая умеет делать спрайты на сервере.
И последний момент перед тем, как мы посмотрим на результат нашей работы. Нам необходимо предохраниться от ситуации, когда браузер не умеет работать с псевдоселекторами типа :checked (типа IE 6 или 7). Для этого изменим стили таким образом:
body {
padding: 10px;
background-color: #363636;
}
.option-group:not(#foo) > input[type='radio'] {
opacity: 0;
filter: alpha(opacity=0);
width: 17px;
height: 17px;
z-index:99999;
position: relative;
cursor: pointer;
}
.option-group:not(#foo) > input[type='radio'] + label {
padding-left: 24px;
background: url('https://s3-eu-west-1.amazonaws.com/radiantwind/js_css/radio_sprite.png') transparent no-repeat 0 -18px;
position: relative;
left: -21px;
font-size: 16px;
color: #efefef;
cursor: pointer;
height: 17px;
}
.option-group:not(#foo) > input[type='radio']:checked + label {
background-position: 0 0;
}
Всё осталось без изменений, за исключением того, что мы дописали .option-group:not(#foo) > . Эта запись говорит о том, что стили следует применять только к тем объектам, которые непосредственно вложены в .option-group и при этом .option-group не должен иметь идентификатора #foo. Понятное дело, что такого идентификатора у нас нет, это условие всегда обращается в истинное, поэтому все современные браузеры будут применять данные стили всегда. А вот браузеры, которые не знают, что такое :not (и :checked), пропустят эти инструкции, таким образом, оставив стандартные радиокнопки без изменений - получается, что мы добавили обратную совместимость. И вот, наконец, конечный результат:
Обратите внимание на рисунок: на нём синим цветом помечена наша настоящая, скрытая радиокнопка, которая расположена точно под фоновым рисунком нашего лейбла - таким образом, мы "обманываем" пользователя и достигаем нужного результата. Вместе с этим, если браузер не умеет работать с псевдоселекторами, он просто не применит стили, связанные с сокрытием радиокнопок и заданием нового фонового рисунка для лейблов, и пользователь также сможет работать с формой.
Вот таким образом мы сумели стилизовать радиокнопки (и, в перспективе, флажки) для нашего сайта. Данный подход хорош тем, что не требуется писать никаких скриптов, а используемые стили достаточно простые. Его минус состоит в том, что более старые браузеры не умеют работать с используемыми псевдоселекторами, хотя для этого случая мы внедрили обратную совместимость. Этот подход не единственный - в Интернете рассказывается о других способах решения данной задачи, зачастую с использованием скриптов. Попробуйте несколько вариантов и решите, какой более подходит в вашем случае.
Удачной работы, увидимся в следующих постах!