Стремительное развитие программного обеспечения, а параллельно с ним ещё более резвый рост производительности компьютерного оборудования, растворяют в себе злободневность полемики об эффективном коде. И вот уже в очередном проекте 2-3-страничного сайта нам проще использовать нечто типа jQuery(‘.spoiler’).show(), чтобы оживить статичный сайт. Ведь за мощью компьютера совсем не заметно расточительство ресурсов, вызванное цепочкой внутри библиотечных действий от такой команды, и только ради организации простейшей бизнес-логики на клиентской стороне.
Нынче в том нет ничего зазорного, ведь эволюция ПО и техники позволяет программисту решать задачу, не заботясь о цене и вникании в тонкости процесса. Однако для повышения кругозора и как замечательный экспонат в кунсткамеру, дам ссылку на пример того, что определённая часть бизнес-логики сайта, посвящённая оживлению статики, в принципе могла быть реализована вообще без применения скриптовых технологий.
Любителям сначала поразгадывать ребус — как же мне удалось запрограммировать такой интерфейс на CSS — следуйте на страницу примера. Если не отгадаете, загляните за подсказкой в исходный код примера. Там всё прокомментировано и разложено по секциям. Остальных приглашаю под кат, где изложу суть этого механизма.
Всё построено на особенностях двух селекторов:
- родственного (+) — он указывает на первого соседа справа, то есть на элемент, размещённый в html-разметке следом за опорным элементом, например (сразу извиняюсь за познавательный стиль написания кода, только чтобы понимал и неопытный человек, здесь привлеку вымышленные русские названия вместо тегов и ниже вместо имён классов)
ОПОРНЫЙ + СОСЕД { стили, применяемые к соседу справа }
- обобщённого родственного (~) — он указывает на всех соседей справа, то есть на всякий элемент, размещённый в html-разметке сразу же после или на некотором отдалении от опорного элемента, но обязательно на том же уровне иерархии (то есть имеющих того же родителя, что и опорный элемент), например
ОПОРНЫЙ ~ СОСЕД { стили, применяемые к каждому соседу справа }
Создаём навигаторы — кнопки, флажки, переключатели, тумблеры
Существует в HTML удобная самоуправляющаяся связка тегов — это <label><input></label>, где опорным элементом для нас выступил бы тег <input>, саму же связку легко стилизовать как под кнопку, так и переключатель. Однако в CSS3 не предусмотрен селектор, который бы указывал, что стили будут применяться не к его концевому элементу, а какому-то предшествующему в строке селектора. Такая особенность появится только в CSS4.
Выйти из положения позволит родственный селектор. Только тег <input> придётся вынести перед тегом <label>, то есть сделать их ближайшими соседями. Это даст хранить состояние воображаемой кнопки за счёт того, что оно уже хранится флажком, и управлять стилями кнопки за счёт того, что она является правым соседом флажка. Причём флажок становится лишь тенью, а потому с помощью стилей вообще скрывается от показа на странице.
Вот как это выглядит в html-разметке (на CSS4 атрибуты id, name, for не понадобились бы, здесь они используются лишь для пометки — что с чем связано и где теневой элемент):
<input id="меткаСвязи" name="меткаТени" type="checkbox" /> <label class="типНавигатора меткаНавигатора" for="меткаСвязи"> текст кнопки </label>
Так это выглядит в стилях (здесь скрываем теневой элемент и стилизуем кнопку согласно её типу, скажем это могли быть «кнопка», «флажок», «переключатель», «тумблер» и так далее — сколько бы нам понадобилось разных видов навигаторов):
[name="меткаТени"] { display: none; } [name="меткаТени"] + .типНавигатора { стили для кнопки данного типа }
Создаём общие обработки событий
В отличие от частных обработок, предназначенных для отмеченного навигатора на странице, общие обработки задают стиль всякого навигатора соответствующего типа при наступлении определённого события. Например, курсор над кнопкой. Например, тумблер в положении ВКЛЮЧЕНО. И тому подобное.
[name="меткаТени"] + .типНавигатора:hover { стили, когда курсор над кнопкой } [name="меткаТени"]:checked + .типНавигатора { стили, когда находится в состоянии ВКЛЮЧЕНО } [name="меткаТени"]:not(:checked) + .типНавигатора { стили, когда в состоянии ВЫКЛЮЧЕНО } [name="меткаТени"]:disabled + .типНавигатора { стили, когда в состоянии ЗАПРЕЩЁН } [name="меткаТени"]:not(:disabled) + .типНавигатора { стили, когда в состоянии РАЗРЕЩЁН } [name="меткаТени"]:indeterminate + .типНавигатора { стили, когда в неопределённом состоянии }
Создаём части контента
Здесь всё как обычно — тривиальные блоки html-разметки, в которых располагаем контент как нам удобно. Только части, какие будут управляться навигаторами, необходимо снабдить какой-нибудь уникальной меткой, чтобы к этим частям можно было сослаться. Например
<form> ля-ля-ля <div class="управляемыйЭлемент меткаСпойлера1"> некое уточнение </div> <div> ля-ля-ля <div class="управляемыйЭлемент меткаУведомления1"> Не заполнили имя! </div> ля-ля-ля <div class="управляемыйЭлемент меткаУведомления2"> Не заполнили емейл! </div> ля-ля-ля </div> <div class="управляемыйЭлемент изначальноВидимый меткаПанели1"> выдвигающаяся панель </div> ля-ля-ля </form> <div class="управляемыйЭлемент изначальноВидимый меткаУведомления3"> Заполните предложенную форму! </div>
И в стилях прописать внешний вид управляемых элементов. Например они изначально не видны, кроме явно помеченных, и раскрашены цветами.
.управляемыйЭлемент { display: none; } .управляемыйЭлемент.изначальноВидимый { display: block; } .меткаУведомления1, .меткаУведомления2 { color: red; } .меткаУведомления3 { color: green; } .меткаПанели1 { width: 20px; }
Необходимо учесть, что стилизационный доступ к управляемым частям будет происходить с помощью обобщённого родственного селектора, следовательно такие части не могут располагаться в html-разметке выше навигатора, со стороны которого инициируется доступ к управляемому элементу.
Кроме того, корневые узлы DOM-веток, в которых размещены управляемые элементы, должны быть одноуровневыми соседями навигаторов.
Создаём частные обработки событий
Эти обработки похожи на общие, только задаются в отношении конкретного навигатора и с применением обобщённого родственного селектора. Например по включению Кнопки1 раздвинем Панель1.
[name="меткаТени"]:checked + .меткаКнопки1 ~ .меткаПанели1, [name="меткаТени"]:checked + .меткаКнопки1 ~ * .меткаПанели1 { width: 300px; }
Двойная запись здесь означает, что панель либо размещена по соседству с кнопкой, либо в каком-то из соседей кнопки.
Ещё пример — по включению Кнопки1 покажем Спойлер1.
[name="меткаТени"]:checked + .меткаКнопки1 ~ .меткаСпойлера1, [name="меткаТени"]:checked + .меткаКнопки1 ~ * .меткаСпойлера1 { display: block; }
Ещё пример — по выключению Флажка1 скроем Уведомление3.
[name="меткаТени"]:not(:checked) + .меткаФлажка1 ~ .меткаУведомления3, [name="меткаТени"]:not(:checked) + .меткаФлажка1 ~ * .меткаУведомления3 { display: none; }
Иногда придётся прибегнуть к !important, чтобы действие одних обработок не перекрыло стилизацию логически более важных обработок. Ведь порядок обработки стилей подчиняется собственным правилам.
Очевидные недостатки:
- особенности обобщённого родственного селектора вынуждают располагать навигатор в html-разметке ранее управляемой части контента;
- те же особенности селектора не дают размещать навигатор в глубине другой DOM-ветки, чтобы он не имел прямого соседства с DOM-веткой управляемого контента (это появится в CSS4);
- отсутствие селектора прямого родителя вынуждает выносить теневой флажок перед кнопкой в html-разметке и добавлять во флажок и кнопку лишние атрибуты, только чтобы указать их связанность, а также порождает лишние конструкции в стилях (это появится в CSS4);
- проблема разрозненности теневого флажка и кнопки может быть решена и в CSS3 за счёт отказа от кнопки и превращения флажка в неё (более точно, кнопку подменит псевдо элемент :before или :after), однако не все браузеры поддерживают такое превращение, чтобы не вмешиваться в нашу стилизацию (отдельные атрибуты оказываются не перекрываемыми, например -moz-appearance: none не действует на <input type=»checkbox»> в Firefox).
От автора:
- в примере я обошёл тему анимации слайдера, она не являлась целью примера, потому сделана простая — показать / скрыть элемент, попробуйте поиграть свойством transition или эффектами из animate.css, если вам это интересно;
- вы можете делать бесплатные или коммерческие модули, управляемые чисто на CSS? — с удовольствием размещу информацию о них на своей странице модулей.
Скриншот примера в полный размер: