- PVSM.RU - https://www.pvsm.ru -
Тема, мягко говоря, не новая, существует ряд статей — на Smashing Magazine [1] и в блогах [2], а так же просто реализации [3] (исходный код [4], только та часть, которая касается анимации). Но, помимо фатального недостатка, у данных реализаций есть недостатки фактические — первые два варианта не предоставляют управления, а последний хоть и предоставляет, но при переключении слайдов анимация останавливается и её приходится запускать снова. Пожалуй, можно сказать что это фича, но мне хотелось полностью спародировать поведение слайдшоу как если бы оно было написано на javascript (что в итоге всё равно не удалось) — то есть при переклчении анимация продолжается, но начинается с выбранного слайда.
Кому лень читать — сразу конечный результат [5].

В варианте, представленном на Smashing Magazine [1], для каждого слайда саздается своя анимация —
#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
Результат [6] на данном этапе.
$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
Результат [7] на данном этапе.
Правда, если в предыдущем случае css рос линейно при увеличении количества слайдов, то теперь имеет место квадратичная зависимость. Но, мы договорились об этом не думать.
… невозможна сама по себе. То есть мы не можем переключить их с помощью CSS. Но мы можем анимировать <label>, связанный с инпутом, а сами инпуты можно скрыть. Но вот незадача — хром и другие вебкиты реагируют на клик по интпуту и клик по метке по-разному — во втором случае анимация меняется только после второго клика по метке (быть может, кто-нибудь в курсе, почему так происходит?) Причем инпут переключается, но анимация при этом не меняется. Соответственно, нам нужен именно клик по инпуту. Для этого мы можем расположить его над меткой и сделать его прозрачным. Нужно понимать, что подсвеченная метка никак не связана с активным инпутом — это просто анимация, которая идет независимо, но задержки анимаций точно такие же, как и для слайдов. Важно, чтобы метки шли после всех инпутов.
// добавляется ещё один @keyframes
@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
Результат [8] на данном этапе.
Как было сказано выше, для смены нужно уметь хранить и менять состояние. Хранить мы его уже умеем, значит стрелочки влево-вправо должны лишь менять текущий активный инпут. Первый вариант — это в каждом состоянии (при каждом активном инпуте) у нас будет по две метки — первая привязана в предыдущему инпуту, вторая — к следующему (циклически). (В качестве идеи: можно не создавать дополнительные инпуты, а стрелочки положить в :after и :before соответствующей метки внизу. Правда, здесь могут (?) возникнуть приблемы с анимированием псевдоэлементов.)
При этом нужно учитывать, что при клике по метке анимация остановится, а вне анимации по умолчанию размеры метки равны нулю, то есть она невидима. И поэтому ничего не сработает — оказалось, мы должны не только кликнуть по лейблу, но и отпустить кнопку мыши над тем же элементом. Так что активную метку нужно показывать всегда — label:active { font‑size: 30px; }
Появление 'правильной' стрелочки можно добиться, например, циклическим сдвигом атрибутов 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
Здесь мы снова сталкиваемся с тем, что в вебкитах клик по лейблу срабатывает только со второго раза (все-таки, почему?), то есть стрелочки реагируют только на двойной клик. В мозиле работает как надо.
Конечный результат. [5]
Ничего хорошего. CSS даже для четырех элементов получается огромный (260 строк [9]).
Материалы:
Ссылки:
Автор: linoleum
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/css/74562
Ссылки в тексте:
[1] Smashing Magazine: http://www.smashingmagazine.com/2012/04/25/pure-css3-cycling-slideshow/
[2] блогах: http://themarklee.com/2013/10/16/simple-crossfading-slideshow-css/
[3] реализации: http://www.creativejuiz.fr/trytotry/juizy-slideshow-full-css3-html5/
[4] исходный код: http://pastebin.com/mMFRYTWn
[5] конечный результат: http://codepen.io/MartinSchulz/pen/RNwyaE
[6] Результат: http://codepen.io/MartinSchulz/pen/enxoJ
[7] Результат: http://codepen.io/MartinSchulz/pen/Cjdxz
[8] Результат: http://codepen.io/MartinSchulz/pen/zxYdxd
[9] 260 строк: http://pastebin.com/3Yz9Fjy8
[10] CSS animation: http://css-tricks.com/snippets/css/keyframe-animation-syntax/
[11] Документация Sass: http://sass-lang.com/documentation/file.SASS_REFERENCE.html
[12] Управяющие директивы Sass: http://thesassway.com/intermediate/if-for-each-while
[13] Документация Jade: http://jade-lang.com/reference/code/
[14] Источник: http://habrahabr.ru/post/202844/
Нажмите здесь для печати.