Напишем слайдеры изображений на чистых HTML и СSS. Меняем только CSS, разметка в HTML остается неизменной. Внешний вид из-за разного CSS при этом разительно различается, а в слайдеры можно вставить неограниченное число картинок. Сначала мы создали круговой слайдер с бесконечным вращением, похожий на виджет-спиннер с изображениями. Затем мы сделали слайдер, пролистывающий стопку фотографий. Продолжение — к старту курса по Fullstack-разработке на Python.


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


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



Теперь, когда хорошо понятно расположение картинок, начнем писать код.


Основные настройки


Начнем с HTML-кода, который мы использовали при работе с другими слайдерами:


<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

Мы снова используем CSS Grid для того, чтобы расположить изображения стопкой, одно поверх другого:


.gallery {
  display: grid;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 160px;
  aspect-ratio: 1;
  object-fit: cover;
}

Анимация


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


Для кругового слайдера мы воспользовались CSS-свойством transform-origin и animation-delay. Одна и та же анимация применялась ко всем изображениям, вращающимся вокруг определенной точки. Затем, чтобы разместить изображения по кругу, мы включили различные задержки.


Для 3D-слайдера логику придется немного изменить. Воспользоваться transform-origin не получится, ведь работать мы будем с тремя измерениями. Вместо этого свойства применим transform, чтобы разместить все картинки должным образом, а после повернем контейнер.


Для прохода циклом по изображениям и применению к каждому свойству transform поработаем с SASS:


@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
     transform: 
       rotate(#{360*($i - 1) / $n}deg) /* 1 */
       translateY(50% / math.tan(180deg / $n)) /* 2 */ 
       rotateX(90deg); /* 3 */
  }
}

Вы можете спросить, почему сразу начать использовать SASS. В предыдущих статьях мы сначала работали с одним и тем же числом изображений, применяя чистый CSS, и только потом переходили к SASS для работы с переменным числом изображений N. Мне кажется, что вы уже достаточно разобрались в теме, и можно сразу перейти к воплощению идеи на SASS.


Здесь можно увидеть, что свойство transform принимает три значения:


Демонстрация трех состояний макета слайдера картинок


Сначала поворачиваем картинки, расположенные стопкой. Угол поворота зависит от числа картинок. Для N изображений изменение угла составит 360deg/N. Затем применяем свойство translate ко всем изображениям так, чтобы они пересекались центральными точками по бокам.


Демонстрация стопки изображений, расположенных по кругу. Красная линия проходит через центральные точки изображений.


Не будем углубляться в геометрию. Просто примите как данность, что расстояние равно 50%/tan(180deg/N). Со схожим уравнением мы сталкивались, когда создавали круговой слайдер ( transform-origin: 50% 50%/sin(180deg/N) ).


Далее поворачиваем изображения по оси Х на 90 градусов. Здесь можно посмотреть, к чему приводит такой поворот:


Наконец, необходимо придать вращение контейнеру, и наш слайдер заработает.


.gallery {
  transform-style: preserve-3d;
  --_t: perspective(280px) rotateX(-90deg);
  animation: r 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes r {
  0%, 3% {transform: var(--_t) rotate(0deg); }
  @for $i from 1 to $n {
    #{($i/$n)*100 - 2}%, 
    #{($i/$n)*100 + 3}% {
      transform: var(--_t) rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: var(--_t) rotate(-360deg); }
}

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


.gallery {
  animation: m 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes m {
  0%, 3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%,
    #{($i / $n) * 100 + 3}% { 
      transform: rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: rotate(-360deg); }
}

Ключевые кадры почти одинаковы — повторяются процентные значения, цикл и поворот.


Почему они повторяются? Потому что логика та же. В обоих случаях изображения размещаются по кругу, и для показа каждой картинки контейнер нужно поворачивать. Вот почему я могу скопировать код ключевых кадров из кругового слайдера в трехмерный. Единственное отличие — угол поворота контейнера. Повернуть его нужно по оси Х и на -90 градусов (-90deg), чтобы картинки стали видны, ведь контейнер мы уже повернули на 90 градусов по той же оси X. Теперь добавляем свойство perspective и получаем трехмерное изображение.


Вот и все. Наш слайдер готов. Ниже вы видите демо-версию. Вам нужно лишь добавить любое число изображений и обновить одну переменную.


Вертикальный трехмерный слайдер


Раз уж мы занялись освоением трехмерного пространства, давайте создадим вертикальную версию нашего слайдера. Наш слайдер вращается по оси Z, но можно заставить его вращаться по оси Х.



При сравнении кода вертикального и горизонтального слайдера различие не сразу бросается в глаза — ведь разница всего в одной букве! Я заменил rotate() на rotateX() в коде, отвечающем за трансформацию (transform) ключевых кадров изображений. Вот и все!


Стоит отметить, что rotate() (поворот) вращает по оси Z, и изменив ось с Z на X мы создали вертикальный слайдер из горизонтального.


Кубический слайдер


Если говорить о 3D в CSS, нельзя не упомянуть о кубах. И да, сейчас мы напишем еще одну версию слайдера.


Создадим куб из картинок и будем поворачивать его по различным осям. Работать будем с шестью гранями — каждое изображение станет гранью куба. Никакого SASS — только чистый CSS.



Вам не кажется, что такая анимация слишком сложная? Откуда же начать?


Мы видим шесть граней, и нам нужно осуществить по крайней мере шесть вращений, чтобы увидеть каждое изображение. Но на самом деле, нам нужно пять вращений — последнее вращение возвращает нас к первой грани. Если вы возьмете кубик Рубика или любой другой предмет в форме куба, к примеру, игральную кость, и начнете вращать его, вы поймете, что мы сделаем.


.gallery {
  --s: 250px; /* the size */

  transform-style: preserve-3d;
  --_p: perspective(calc(2.5*var(--s)));
  animation: r 9s infinite cubic-bezier(.5, -0.5, .5, 1.5);
}

@keyframes r {
  0%, 3%   { transform: var(--_p); }
  14%, 19% { transform: var(--_p) rotateX(90deg); }
  31%, 36% { transform: var(--_p) rotateX(90deg) rotateZ(90deg); }
  47%, 52% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
  64%, 69% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg); }
  81%, 86% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg); }
  97%, 100%{ transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
}

Первое свойство transform не поворачивает элемент, а затем каждое новое свойство добавляет поворот по определенной оси до тех пор, пока мы не сделаем шесть поворотов. Затем мы возвращаемся к первому изображению.


Не забываем про расположение картинок. Каждое изображение становится гранью куба с помощью свойства transform:


.gallery img {
  grid-area: 1 / 1;
  width: var(--s);
  aspect-ratio: 1;
  object-fit: cover;
  transform: var(--_t,) translateZ(calc(var(--s) / 2));
}
.gallery img:nth-child(2) { --_t: rotateX(-90deg); }
.gallery img:nth-child(3) { --_t: rotateY( 90deg) rotate(-90deg); }
.gallery img:nth-child(4) { --_t: rotateX(180deg) rotate( 90deg); }
.gallery img:nth-child(5) { --_t: rotateX( 90deg) rotate( 90deg); }
.gallery img:nth-child(6) { --_t: rotateY(-90deg); }

Возможно, вам кажется, что за прописанными здесь значениями поворота стоит какая-то сложная логика? Отнюдь. Я просто открыл инструменты разработчика и поиграл с градусами поворота каждой картинки, пока все не заработало, как надо. Возможно, это звучит глупо — но это работает, особенно если учесть, что количество изображений у нас постоянное. Здесь нет стремления создать слайдер для произвольного числа картинок.


В качестве упражнения попробуйте сами расположить картинки на гранях куба. Начните с расположения картинок стопкой, откройте инструменты разработчика и начинайте экспериментировать! Скорее всего, у вас получится другой код, но это нормально. Изображения можно расположить по-разному.


А что означает запятая в var()? Это опечатка?

Это не опечатка, не удаляйте запятую! Если вы ее удалите, расположение первой картинки изменится. Посмотрите на мой код — в нем переменная --_t отвечает за все изображения, кроме первого. Для первой картинки необходимо применить только translateZ. Запятая присваивает переменной значение null. Без запятой присваивания null не происходит, и значение переменной становится некорректным.


Из спецификации:


Примечание: var(--a,) — это функция. Если пользовательское свойство --a неверно или отсутствует, то var()` удаляется.

Случайный кубический слайдер


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



Мне такой вариант нравится больше. Его интереснее реализовать, и за ним интереснее наблюдать. А значения можно изменить, чтобы получить собственный вариант случайного кубического слайдера!


На самом деле, случайности здесь нет. На каждом ключевом кадре вы определяете свойство transform для демонстрации определенной грани… и все. Можно выбирать любую последовательность показа граней.


@keyframes r {
  0%, 3%   { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
  14%,19%  { transform: var(--_p) rotate3d(-1, 1, 0,180deg); }
  31%,36%  { transform: var(--_p) rotate3d( 0,-1, 0, 90deg); }
  47%,52%  { transform: var(--_p) rotate3d( 1, 0, 0, 90deg); }
  64%,69%  { transform: var(--_p) rotate3d( 1, 0, 0,-90deg); }
  81%,86%  { transform: var(--_p) rotate3d( 0, 1, 0, 90deg); }
  97%,100% { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
}

Здесь я использую rotate3d() и подбираю подходящие значения с помощью инструментов разработчика. Не пытайтесь понять логику, связывающую значения ключевых кадров — ее просто нет. Я определяю отдельные трансформации и смотрю на "случайный" результат. Убедитесь, что первое изображение присутствует на первом и последнем ключевом кадре, а на остальных кадрах показываются другие картинки.


Вам не обязательно использовать трансформацию rotate3d(). Вы можете связать вместе различные повороты, как в предыдущем примере. Экспериментируйте, смотрите, что получается и не забудьте поделиться своим кодом в комментариях!


Заключение


Надеюсь, вам понравилась эта серия статей. Мы создали ряд интересных слайдеров, и в процессе узнали много нового о различных концепциях CSS — от расположения грида и контекста наложения до задержек в анимации и трансформаций. Мы даже немного поиграли с SASS, когда проходили циклом по массиву элементов.


И при этом мы не меняли HTML-код. CSS — мощный инструмент, с его помощью можно достичь многого, не прибегая к JavaScript.




Комментарии (2)


  1. megaslowpoke
    18.01.2023 21:15

    Красиво конечно, но даже один такой "слайдер" в области видимости тормозит скрол на слабой видюхе или не успевшей переключить performance state в максимум.


  1. metanolfmkfk
    20.01.2023 14:40

    Горизонтальный слайдер. При преобразовании SCSS в CSS через коалу в translateY(50%/math.tan(180deg/$n)) выдаёт ошибку:
    Error: Invalid CSS after "...slateY(50%/math": expected was ".tan(180deg/$n))" on line 29 of C:\OSPanel\domains\carousel\sass\carousel.scss

    Use -trace for backtrace.
    Полюбому я рукопоп, но просто стало интересно чужой код через коалу покомпилировать....