Пример слайдера, управляемого только с помощью CSS3



Дмитрий Сахань

Стремительное развитие программного обеспечения, а параллельно с ним ещё более резвый рост производительности компьютерного оборудования, растворяют в себе злободневность полемики об эффективном коде. И вот уже в очередном проекте 2-3-страничного сайта нам проще использовать нечто типа jQuery('.spoiler').show(), чтобы оживить статичный сайт. Ведь за мощью компьютера совсем не заметно расточительство ресурсов, вызванное цепочкой внутри библиотечных действий от такой команды, и только ради организации простейшей бизнес-логики на клиентской стороне.

Нынче в том нет ничего зазорного, ведь эволюция ПО и техники позволяет программисту решать задачу, не заботясь о цене и вникании в тонкости процесса. Однако для повышения кругозора и как замечательный экспонат в кунсткамеру, дам ссылку на пример того, что определённая часть бизнес-логики сайта, посвящённая оживлению статики, в принципе могла бы быть реализована вообще без применения скриптовых технологий.

Любителям сначала поразгадывать ребус — как же мне удалось запрограммировать такой интерфейс на CSS — следуйте на страницу примера. Если не отгадаете, загляните за подсказкой в исходный код примера. Там всё прокомментировано и разложено по секциям. Остальным рекомендую читать дальше, где изложена суть этого механизма.

Всё построено на особенностях двух селекторов

Родственного (+) — он указывает на первого соседа справа, то есть на элемент, размещённый в html-разметке следом за опорным элементом, например (в данном случае опорным выступает элемент h1 и соседом — элемент p):

h1 + p {
    /* стили, применяемые к соседу справа */
}

Обобщённого родственного (~) — он указывает на всех соседей справа, то есть на всякий элемент, размещённый в html-разметке сразу же после или на некотором отдалении от опорного элемента, но обязательно на том же уровне иерархии (то есть имеющих того же родителя, что и опорный элемент), например:

h1 ~ p {
    /* стили, применяемые к каждому соседу справа */
}

Создаём навигаторы — кнопки, флажки и т.д.

Существует в HTML удобная самоуправляющаяся связка тегов — это <label><input type="checkbox"></label>, где опорным элементом для нас выступил бы тег <input>, саму же связку легко стилизовать как под кнопку, так и под переключатель. Однако в CSS3 не предусмотрен селектор, который бы указывал, что стили будут применяться не к его концевому элементу, а какому-то предшествующему элементу. Такая особенность появится только в CSS4.

Выйти из положения позволит родственный селектор. Только тег <input> придётся вынести перед тегом <label>, то есть сделать их ближайшими соседями. Это даст возможность хранить состояние воображаемой кнопки за счёт того, что оно уже хранится флажком, и управлять стилями кнопки (ею выступит сам <label>) за счёт того, что она является правым соседом флажка. Поскольку флажок выступает лишь как хранитель состояния, с помощью стилей мы вообще скрываем его от показа на странице.

Вот как это выглядит в html-разметке (на CSS4 атрибуты id, name, for не понадобились бы, здесь они используются лишь для пометки — что с чем связано и где теневой элемент):

<input id="relation1" name="shadow-button1" type="checkbox">
<label class="button key1" for="relation1">
    текст кнопки
</label>

Чтобы вам было понятно, имя shadow-... в теге <input> и класс button в теге <label> — это те части имён, что используем ниже в стилях для указания на элементы. Класс key1 является здесь фиктивным и предназначен лишь для снабжения кнопки некой уникальной меткой, посредством которой в дальнейшем можно указать конкретно на эту кнопку в стилях. Не забывайте также, что это можно сделать и с помощью атрибута id кнопки, кому какой способ маркировки больше нравится. Однако маркировку через идентификаторы нужно использовать осторожно, чтобы на странице отсутствовали элементы с одинаковыми идентификаторами (в модульных шаблонах такая проблема случается, когда на страницу подключают в разных местах один и тот же модуль, скажем пагинация страниц над и под контентом). Ниже будет упомянута ещё одна причина маркировки без применения идентификаторов.

Теперь рассмотрим, как это выглядит в стилях (здесь скрываем теневой элемент и стилизуем кнопку каждого вида согласно её типу, скажем это могли быть «кнопка», «флажок», «переключатель», «тумблер» и так далее — сколько бы нам понадобилось разных видов навигаторов):

[name^="shadow-"] {
    display: none;
}

[name^="shadow-"] + .button {
    /* стили для кнопки данного типа */
}

[name^="shadow-"] + .tumbler {
    /* стили для кнопки ещё какого-то типа */
}

[name^="shadow-"] + .switch {
    /* стили для кнопки третьего типа */
}

[name^="shadow-"] + .checkbox {
    /* стили для кнопки четвертого типа */
}

Запись вида [name^="shadow-"] указывает на всякий элемент страницы, теговый атрибут name которого начинается с символьной последовательности shadow-.

Создаём общие обработки событий

В отличие от частных обработок, предназначенных для конкретного навигатора на странице, отмеченного неким уникальным маркером, общие обработки задают стиль всякого навигатора соответствующего типа при наступлении определённого события. Например, курсор над кнопкой, тумблер в положении ВКЛЮЧЕНО и тому подобное.

[name^="shadow-"] + .tumbler:hover {
    /* стили, когда курсор над кнопкой */
}

[name^="shadow-"]:checked + .tumbler {
    /* стили, когда находится в состоянии ВКЛЮЧЕНО */
}

[name^="shadow-"]:not(:checked) + .tumbler {
    /* стили, когда в состоянии ВЫКЛЮЧЕНО */
}

[name^="shadow-"]:disabled + .tumbler {
    /* стили, когда в состоянии ЗАПРЕЩЁН */
}

[name^="shadow-"]:not(:disabled) + .tumbler {
    /* стили, когда в состоянии РАЗРЕЩЁН */
}

[name^="shadow-"]:indeterminate + .tumbler {
    /* стили, когда в неопределённом состоянии */
}

Поскольку у нас может быть несколько видов навигаторов — кнопки, тумблеры, флажки — для каждого прописываем желаемые общие обработки.

Создаём части контента

Здесь всё как обычно — тривиальные блоки html-разметки, в которых располагаем контент как нам удобно. Только части, какие будут управляться навигаторами, необходимо снабдить какой-нибудь уникальной меткой (маркером), чтобы к этим частям можно было бы обратиться. Например

<form>
    ля-ля-ля

    <div class="controlled spoiler1">
        некое уточнение
    </div>

    <div>
        ля-ля-ля

        <div class="controlled message1">
            Не заполнили имя!
        </div>

        ля-ля-ля

        <div class="controlled message2">
            Не заполнили емейл!
        </div>

        ля-ля-ля
    </div>

    <div class="controlled visible panel1">
        выдвигающаяся панель
    </div>

    ля-ля-ля
</form>

<div class="controlled visible message3">
    Заполните предложенную форму!
</div>

Ради ясности упомяну имена классов — controlled и visible, они используются лишь как средство для обозначения управляемых элементов или изменения их внешнего вида. Совсем не значит, что вы обязаны использовать для этих классов такие же имена.

И теперь в стилях пропишем внешний вид управляемых элементов. Например они изначально не видны, кроме явно помеченных, и раскрашены цветами.

.controlled {
    display: none;
}

.controlled.visible {
    display: block;
}

.message1,
.message2 {
    color: red;
}

.message3 {
    color: green;
}

.panel1 {
    width: 20px;
}

Выше говорилось, что маркировку элементов допустимо делать как с помощью фиктивных классов, так и с помощью атрибута id. Но учитывая, что селекторы применяются в CSS последовательно согласно их весу, в вычислении которого идентификатор элемента играет не последнюю роль (его вес только равен 100), может так случиться, что стилевые правила обработок по весу не произведут должного эффекта, если начать использовать маркировку смешанную — где-то фиктивными классами, где-то идентификаторами. Рекомендуется придерживаться одного стиля маркировки. Более того, если по задаче потребуется как-то перекрывать эффект от отдельных обработок событий (скажем indeterminate-подсветка должна действовать на все радио-флажки, кроме конкретного), маркировка за счёт фиктивных классов окажется выгоднее, потому что их вес малый и может быть перекрыт более весовым селектором элемента, исключаемого из такой обработки.

Кроме того необходимо учесть, что стилизационный доступ к управляемым частям будет происходить с помощью обобщённого родственного селектора, следовательно такие части не могут располагаться в html-разметке выше навигатора, со стороны которого инициируется доступ к управляемому элементу.

В дополнение корневые узлы DOM-веток, в которых размещены управляемые элементы, должны быть одноуровневыми соседями навигаторов.

Создаём частные обработки событий

Эти обработки похожи на общие, только задаются в отношении конкретного навигатора и с применением обобщённого родственного селектора. Например по включению кнопки key1 раздвинем панель panel2.

[name^="shadow-"]:checked + .key1 ~ .panel2,
[name^="shadow-"]:checked + .key1 ~ * .panel2 {
    width: 300px;
}

Двойная запись здесь означает, что панель либо размещена по соседству с кнопкой, либо в каком-то из соседей кнопки.

Ещё пример — по включению кнопки key3 покажем спойлер spoiler1.

[name^="shadow-"]:checked + .key3 ~ .spoiler1,
[name^="shadow-"]:checked + .key3 ~ * .spoiler1 {
    display: block;
}

Ещё пример — по выключению флажка checkbox1 скроем уведомление message3.

[name^="shadow-"]:not(:checked) + .checkbox1 ~ .message3,
[name^="shadow-"]:not(:checked) + .checkbox1 ~ * .message3 {
    display: none;
}

Иногда придётся прибегнуть к !important, чтобы действие одних обработок не перекрыло стилизацию логически более важных обработок. Ведь порядок обработки стилей подчиняется собственным правилам.

Очевидные недостатки

  • Особенности обобщённого родственного селектора вынуждают располагать навигатор в html-разметке ранее управляемой части контента.
  • Те же особенности селектора не дают размещать навигатор в глубине другой DOM-ветки, чтобы он не имел прямого соседства с DOM-веткой управляемого контента (это появится в CSS4).
  • Отсутствие селектора прямого родителя вынуждает выносить теневой флажок перед кнопкой в html-разметке и добавлять во флажок и кнопку лишние атрибуты, только чтобы указать их связанность, а также порождает лишние конструкции в стилях (это появится в CSS4).
  • Проблема разрозненности теневого флажка и кнопки может быть решена и в CSS3 за счёт отказа от кнопки и превращения флажка в неё (более точно, кнопку подменит псевдо элемент :before или :after), однако не все браузеры поддерживают такое превращение, чтобы не вмешиваться в нашу стилизацию (отдельные атрибуты оказываются не перекрываемыми, например -moz-appearance: none не действует на <input type="checkbox"> в Firefox).

От автора

  • В примере я обошёл тему анимации слайдера, она не являлась целью примера, потому сделана простая — показать / скрыть элемент, попробуйте поиграть свойством transition или эффектами из animate.css, если вам это интересно.
  • Вы можете делать бесплатные или коммерческие модули, управляемые чисто на CSS — с удовольствием размещу информацию о них на своей странице модулей.

Ссылки на живое демо

imperacms.ru/examples/css-slider/index.html — полноразмерный скриншот этой страницы продемонстрирован ниже.

Полноразмерный скриншот страницы

Не выкладывайте свой код напрямую в комментариях, он отображается некорректно. Воспользуйтесь сервисом cssdeck.com или jsfiddle.net, сохраните код и в комментариях дайте на него ссылку. Так и результат сразу увидят.