Слайдшоу на CSS (Sass)

Тема, мягко говоря, не новая, существует ряд статей — на Smashing Magazine и в блогах, а так же просто реализации (исходный код, только та часть, которая касается анимации). Но, помимо фатального недостатка, у данных реализаций есть недостатки фактические — первые два варианта не предоставляют управления, а последний хоть и предоставляет, но при переключении слайдов анимация останавливается и её приходится запускать снова. Пожалуй, можно сказать что это фича, но мне хотелось полностью спародировать поведение слайдшоу как если бы оно было написано на javascript (что в итоге всё равно не удалось) — то есть при переклчении анимация продолжается, но начинается с выбранного слайда.Кому лень читать — сразу конечный результат.3039a3291d0049d191b17cad89e40d99.pngСмена слайдов В варианте, представленном на Smashing Magazine, для каждого слайда саздается своя анимация — #slider li.firstanimation { animation: cycle 25s linear infinite; } #slider li.secondanimation { animation: cycletwo 25s linear infinite; } #slider li.thirdanimation { animation: cyclethree 25s linear infinite; } #slider li.fourthanimation { animation: cyclefour 25s linear infinite; } #slider li.fifthanimation { animation: cyclefive 25s linear infinite; } и далее для всех анимаций cycle, cycletwo и т.д. описывается @keyframes и код получается достаточно объемным.Если все слайды анимируются одинаково, то такой вариант избыточен — достаточно создать одну анимацию и задать её для каждого слайда с разным animation‑delay. До n-го элемента будем добираться с помощью : nth‑child. Соответственно, если каждый слайд отображается в течение 3 секунды, то для i‑го слайда задержка будет 3 * (i ‑ 1), например li: nth‑child (1) { animation‑delay:  0s }. В течение всей анимации слайд сначала отображается некоторое время, потом прячется и остается скрытым до конца итерации.

Переменные:

// здесь и далее используется camelCase, т.к. подсветска синтаксиса для php // не понимает дефис в идентификаторах

// количество слайдов $sliderLength: 4 // время, в течение которого слайд отображается $delay: 3s // общая продолжительность анимации $duration: $sliderLength * $delay // время, в течение которого слайд отображается (в процентах) $displayTime: 100% / $sliderLength

@keyframes toggle // изначально элемент скрыт 0% opacity: 0 // затем плавно появляется в течение 10% процентов времени. // Проценты берутся от $delay, а не от общей продолжительности анимации #{$displayTime * 0.1} opacity: 1 // 80% времени мы его видим #{$displayTime * 0.9} opacity: 1 // последние 10% элемент плавно исчезает #{$displayTime} opacity: 0 // и остается скрытым до конца анимации 100% opacity: 0 Сама анимация: // у всех слайдов одна и та же анимация .slider li animation-name: toggle animation-duration: $duration animation-iteration-count: infinite // сокращенная запись // animation: toggle $duration infinite

@for $i from 0 to $sliderLength .slider li: nth-child (#{$i + 1}) // устанавливаем задержку перед анимацией в зависимости от номера элемента animation-delay: $delay * $i Результат на данном этапе.Для тех, кто не знаком с Sass/SCSS $sliderLength: 4 // объявление переменной $delay: 3s // допускаются разные единицы измерения $duration: $sliderLength * $delay $displayTime: 100% / $sliderLength // в том числе проценты

@keyframes toggle // фигурные скобки опускаются 0% opacity: 0 // #{…} используется для вывода данных в строку #{$displayTime * 0.1} opacity: 1 #{$displayTime * 0.9} opacity: 1 #{$displayTime} opacity: 0 100% opacity: 0

.slider li animation: toggle $duration infinite

// обычный for. С одной оговоркой — итерировать будет до $sliderLength — 1 @for $i from 0 to $sliderLength // будет выведено для каждой итерации .slider li: nth-child (#{$i + 1}) animation-delay: $delay * $i Следует понимать, что этот код не совсем честный — не смотря на то, что цикл всего три строчки, для каждого $i будет создано свое css правило — .slider li: nth-child (1) { animation-delay: 0s; } .slider li: nth-child (2) { animation-delay: 3s; } .slider li: nth-child (3) { animation-delay: 6s; } .slider li: nth-child (4) { animation-delay: 9s; } Таким образом, объем css будет [на данном этапе] расти линейно относительно количества слайдов. А посему предлагаю временно забыть о том, что есть css, как будто мы в будущем и у нас умные браурезы, способные эффективно обрабатывать Sass. Без этого допущения читать статью дальше будет страшно.Переход к слайду Для того, чтобы организовать переход к слайду, нам нужно научиться, во-первых, хранить текущее состояние, а во-вторых — уметь это состояние менять. Сделать это можно с помощью radio-инпутов. В зависимости от того, какой инпут активен в данный момент, мы будем просто менять очередность появления элементов. Так, если активен первый инпут, то очеред будет 1,  2,  3,  4, если активен второй — 4,  1,  2,  3 и т.д. То есть, при активном n-ом инпуте мы циклически сдвигаем последовательность на n ‑ 1 позицию вправо. Проверка будет производиться с помощью соседского селектора ~. Например, input: nth‑of‑type (1): checked ~ .slider li (читать — если перед списком первый инпут активен — применить такой-то стиль). При этом инпуты должны располагаться перед списком, в котором лежат слайды.Переменные и @keyframes те же, что и в предыдущем случае.

// при клике на инпут необходимо отменить текущую анимацию. input: active ~ .slider li animation: none! important

.slider li animation: toggle $duration infinite

// для каждого состояния генерируем свой набор правил @for $ctrlNumber from 0 to $sliderLength // все селекторы привязаны к конкретному активному инпуту input: nth-of-type (#{$ctrlNumber + 1}): checked @for $slideNumber from 0 to $sliderLength // получаем сдвиг $position: $slideNumber — $ctrlNumber // сдвиг должен быть циклическим @if $position < 0 $position: $position + $sliderLength ~ .slider li:nth-child(#{$slideNumber + 1}) animation-delay: $delay * $position Результат на данном этапе.Правда, если в предыдущем случае css рос линейно при увеличении количества слайдов, то теперь имеет место квадратичная зависимость. Но, мы договорились об этом не думать.

Подсветка текущего инпута… … невозможна сама по себе. То есть мы не можем переключить их с помощью CSS. Но мы можем анимировать

@keyframes toggle-ctrl #{$display-time * 0.1}, #{$display-time * 0.9} background-color: #555 #{$display-time}, 100% background-color: #ccc Сама же анимация изменится незначительно input: active ~ .slider li, ~ label animation: none! important

.slider li animation: toggle-slide $duration infinite

label animation: toggle-ctrl $duration infinite

@for $ctrlNumber from 0 to $sliderLength input: nth-of-type (#{$ctrlNumber + 1}): checked @for $slideNumber from 0 to $sliderLength $position: $slideNumber — $ctrlNumber @if $position < 0 $position: $position + $sliderLength

~ .slider li: nth-child (#{$slideNumber + 1}), // добавляем правило для лейбла ~ label: nth-of-type (#{$slideNumber + 1}) animation-delay: $delay * $position Результат на данном этапе.Переход к следующему/предыдущему слайду. Как было сказано выше, для смены нужно уметь хранить и менять состояние. Хранить мы его уже умеем, значит стрелочки влево-вправо должны лишь менять текущий активный инпут. Первый вариант — это в каждом состоянии (при каждом активном инпуте) у нас будет по две метки — первая привязана в предыдущему инпуту, вторая — к следующему (циклически). (В качестве идеи: можно не создавать дополнительные инпуты, а стрелочки положить в : after и : before соответствующей метки внизу. Правда, здесь могут (?) возникнуть приблемы с анимированием псевдоэлементов.)При этом нужно учитывать, что при клике по метке анимация остановится, а вне анимации по умолчанию размеры метки равны нулю, то есть она невидима. И поэтому ничего не сработает — оказалось, мы должны не только кликнуть по лейблу, но и отпустить кнопку мыши над тем же элементом. Так что активную метку нужно показывать всегда — label: active { font‑size:  30 px;  }Появление 'правильной' стрелочки можно добиться, например, циклическим сдвигом атрибутов for тега label:

// html (jade)  — for (var i = 0; i < sliderLength; i++) - var id = i - 1 - if (id < 0) id = sliderLength - 1 label.prev(for='c#{id}') ⇚

 — for (var i = 0; i < sliderLength; i++) - var id = i + 1 - if (id === sliderLength) id = 0 label.next(for='c#{id}') ⇛ А можно сделать сдвиг анимаций в CSS: input:active ~ .slide li, ~ .label, ~ .prev, ~ .next animation: none !important

.slide li animation: toggle-slide $duration infinite

.label animation: toggle-ctrl $duration infinite .prev, .next animation: toggle-arrow $duration infinite

@for $ctrlNumber from 0 to $length input: nth-of-type (#{$ctrlNumber + 1}): checked @for $slideNumber from 0 to $length $position: $slideNumber — $ctrlNumber @if $position < 0 $position: $position + $length

~ .slide li: nth-child (#{$slideNumber + 1}), ~ .label: nth-of-type (#{$slideNumber + 1}) animation-delay: $delay * $position // сдвиг для предыдущего $prev: $slideNumber — 1 @if $prev < 0 $prev: $length - 1 // сдвиг для следующего $next: $slideNumber + 1 @if $next == $length $next: 0 // 'type' в данном слечае - это label, // так что необходимо учитывать все предшествующие элементы ~ .prev:nth-of-type(#{$prev + 1 + $length}), ~ .next:nth-of-type(#{$next + 1 + $length * 2}) animation-delay: $delay * $position Здесь мы снова сталкиваемся с тем, что в вебкитах клик по лейблу срабатывает только со второго раза (все-таки, почему?), то есть стрелочки реагируют только на двойной клик. В мозиле работает как надо.Конечный результат.Итого Ничего хорошего. CSS даже для четырех элементов получается огромный (260 строк).Материалы:

Ссылки:

© Habrahabr.ru