Наверх

Создание 3D-вращающейся карусели с CSS и JavaScript

Опубликовано:
3419
1 2 3 4 5
(95%) / 4

Когда я начал изучать эту тему, мне не нужна была трехмерная карусель, но я больше интересовался техническими деталями ее реализации. Разумеется, основные используемые методы, конечно же, относятся к модулю CSS Transforms Module Level 1 , но по мере того, как будет применена группа других технологий разработки переднего плана, затрагивая различные темы в CSS, Sass и клиентском JavaScript .

 

Демо

 

Чтобы проиллюстрировать настройку 3D-преобразований CSS, я покажу вам версию компонента только для CSS. Затем я покажу вам, как улучшить его с помощью JavaScript, разработав простой скрипт компонента.

 

Для разметки изображения внутри компонента обернуты внутри <figure> элемента, который обеспечивает базовый скелет:

<figure style="transform-origin: 50% 50% -600.159px; transform: rotateY(0rad);">
	<img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt="" style="padding: 0px;">
	<img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(0.785398rad);">
	<img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(1.5708rad);">
	<img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(2.35619rad);">
	<img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(3.14159rad);">
	<img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(3.92699rad);">
	<img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(4.71239rad);">
	<img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt="" style="padding: 0px; transform-origin: 50% 50% -600.159px; transform: rotateY(5.49779rad);">
</figure>

Это будет нашим фундаментом.

 

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

Эти <img> элементы должны быть расположены по кругу , очерченном карусели. Этот круг может быть аппроксимирован его ограниченным регулярным многоугольником и изображениями, расположенными по бокам:

Отображение угла тета

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

 

 

Что делать, если в карусели меньше трех изображений?

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

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

 

Вид сверху карусели

Таким образом, сторона, которая в настоящее время обращена к зрителю, будет находиться на плоскости экрана при z = 0, а переднее изображение, не подверженное перспективой ракурса, будет иметь свой обычный 2D размер. d Письмо на картинке представляет значение для CSS perspective свойства.

 

В этом разделе я покажу вам ключевые правила CSS, которые я буду проходить шаг за шагом.

В следующих фрагментах кода некоторые переменные Sass используются, чтобы сделать компонент более настраиваемым. Я буду использовать, $n чтобы обозначить количество изображений в карусели и $item-width указать ширину изображения.

<figure> Элемент является содержащим окном для первого изображения и опорного элемента , вокруг которого другие изображения будут расположены и трансформированны.
Предположим, на данный момент, что у карусели было только одно изображение для демонстрации, я могу начать с калибровки и выравнивания:

.carousel {
  display: flex;
  flex-direction: column;
  align-items: center;
   > * {
     flex: 0 0 auto;
  }

  .figure {
    width: $item-width;
    transform-style: preserve-3d;
    img {
      width: 100%;
      &:not(:first-of-type) {
        display: none /* Just for now */
      }
    }
  }
}

 

<figure> Элемент имеет заданную ширину карусельного элемента , и он имеет такую же высоту изображений (они могут иметь различные размеры , но они должны иметь одинаковое соотношение сторон). Таким образом, высота контейнера карусели адаптируется к высоте изображения. Кроме того, <figure> горизонтально центрируется в карусельном контейнере.

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

Карусель можно поворачивать в трехмерном пространстве, применяя преобразование вращения к <figure> элементу. Это вращение должно быть вокруг центра многоугольника, поэтому я изменю исходное происхождение преобразования <figure>:

.carousel figure {
  transform-origin: 50% 50% (-$apothem);
}

Это значение отрицается, потому что в CSS положительное направление оси z находится вне экрана, к зрителю. Скобки необходимы, чтобы избежать синтаксических ошибок Sass. Вычисление многоугольника apothem будет объяснено позже.

Переведя систему отсчета <figure> элемента, вся карусель может поворачиваться с вращением на (новой) оси y:

.carousel figure {
  transform: rotateY(/* some amount here */rad);
}

Я вернусь к деталям этого поворота позже.

Перейдем к преобразованиям для других изображений. При абсолютном позиционировании изображения складываются внутри <figure>:

.carousel figure img:not(:first-of-type) {
  position: absolute;
  left: 0;
  top: 0;
}

Эти z-index значения игнорируются , потому что это только предварительный шаг для следующих преобразований. Фактически, теперь каждое изображение можно поворачивать по оси y карусели на угол поворота, который зависит от стороны многоугольника, на которой назначено изображение. Во-первых, как это делается с <figure> элементом, изменяется исходное происхождение изображений по умолчанию, перемещая его в центр многоугольника:

.img:not(:first-of-type) {
  transform-origin: 50% 50% (-$apothem);
}

Затем изображения могут поворачиваться на их новой оси y на величину, заданную ($i - 1) * $theta радианами, где $i индекс (начиная с одного) изображения и $theta = 2 * $PI / $n, $PI представляя математическую константу pi . Следовательно, второе изображение будет повернуто $theta третьим 2 * $theta, и так далее, до последнего изображения, которое будет повернуто ($n - 1) * $theta.

Карусельный многоугольник

Это относительное расположение изображений будет сохранено во время поворотов карусели (т.е. Вращения вокруг измененной оси y <figure>) благодаря иерархической природе вложенных преобразований CSS.

Это количество вращения каждого изображения может быть присвоено с помощью @for директивы управления Sass :

.carousel figure img {
  @for $i from 2 through $n {
    &:nth-child(#{$i}) {
      transform: rotateY(#{($i - 1) * $theta}rad);
    }
  }
}

 

Это использует for...through конструкцию, а не for...to потому, что вместо for...to последнего значения, присвоенного переменной индекса $i, будет n-1 вместо n.

Обратите внимание на два примера #{} синтаксиса интерполяции Sass . В первом случае он используется для индекса :nth-child() селектора;
Во втором случае он используется для установки значения свойства вращения.

 

Вычисление Apothem

Вычисление апофемы полигона зависит от количества сторон и ширины стороны, то есть, на $n и $item-width переменных.
Формула:

$image-width / (2 * tan($PI/$n))

Где tan() - касательная тригонометрическая функция .

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

 

На этом этапе изображения карусели «зашиты» бок о бок, образуя требуемую полигональную форму. Но здесь они плотно упакованы, а часто в трехмерных каруселях между ними есть пространство. Это расстояние повышает восприятие 3D-пространства, потому что оно позволяет вам видеть обратные изображения на задней части карусели.

Можно дополнительно добавить этот промежуток между изображениями, введя другую конфигурационную переменную $item-separation и используя ее в качестве горизонтальной прокладки для каждого <img> элемента. Точнее, взяв половину этого значения для левого и правого заполнения:

.carousel figure img {
  padding: 0 $item-separation / 2;
}

 

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

 

Чтобы облегчить тестирование поворота карусели, я собираюсь добавить элемент управления пользовательского интерфейса, чтобы перемещаться между изображениями. См. Демо-версию CodePen для HTML, CSS и JavaScript, реализующих этот элемент управления; Здесь я опишу только код, относящийся к вращению.

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

После обновления currImage, поворот карусели выполняется с помощью:

figure.style.transform = `rotateY(${currImage * -theta}rad)`;

(Здесь и в следующих фрагментах литералы шаблонов ES6 используются для интерполяции выражений в строках, не стесняйтесь использовать традиционный оператор конкатенации «+», если вы предпочитаете)

Где theta то же самое, что и раньше:

numImages = figure.childElementCount;
theta =  2 * Math.PI / numImages;

Вращение происходит - theta из-за того, что для перехода к следующему пункту требуется вращение против часовой стрелки, и такие значения поворота отрицательны в преобразованиях CSS.

Обратите внимание, что это currImageзначение не ограничено диапазоном [0, numImages - 1], но вместо этого оно может расти бесконечно, как в положительном, так и в отрицательном направлении. На самом деле, если изображение на передней панели является последним (так currImage== n-1), и пользователь нажимает следующую кнопку, если мы переустановим currImageна 0, чтобы перейти к первому изображению карусели, произойдет переход Угол поворота от (n-1)*theta до 0, и это повернет карусель в противоположном направлении по всем предыдущим изображениям. Аналогичная проблема может возникнуть при нажатии кнопки prev, когда первое изображение является первым.

Чтобы быть разборчивым, я должен даже проверить потенциальные переполнения currentImage, потому что Number тип данных не может принимать сколь угодно большие значения. Эти проверки не реализованы в демо-коде.

Увидев базовый CSS, лежащий в основе карусели, теперь JavaScript можно использовать для улучшения компонента несколькими способами, такими как:

  • Произвольное количество изображений
  • Изображения с процентной шириной
  • Несколько экземпляров карусели на странице
  • Конфигурации для каждого экземпляра, такие как размер зазора и видимость на обратной стороне
  • Конфигурация с использованием атрибутов данных HTML5 *

Сначала я удаляю из таблицы стилей переменные и правила, связанные с происхождением преобразования и поворотами, потому что это будет сделано с использованием JavaScript:

 

$item-width: 40%; // Now we can use percentages
$item-separation: 0px; // This now is set with Js
$viewer-distance: 500px;

.carousel {
  padding: 20px;

  perspective: $viewer-distance;
  overflow: hidden;

  display: flex;
  flex-direction: column;
  align-items: center;
  > * {
    flex: 0 0 auto;
  }

  figure {
    margin: 0;
    width: $item-width;

    transform-style: preserve-3d;

    transition: transform 0.5s;

    img {
      width: 100%;
      box-sizing: border-box;
      padding: 0 $item-separation / 2;

      &:not(:first-of-type) {
        position: absolute;
        left: 0;
        top: 0;
      }
    }
  }
}

 

Далее в скрипте есть carousel() функция, которая выполняет инициализацию экземпляра:

function carousel(root) {
  // coming soon...
}

 

root Аргумент относится к элементу DOM , который содержит карусель.

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

Чтобы создать несколько компонентов на одной странице, код ожидает, что все изображения будут загружены, зарегистрировав слушателя на объекте окна для loadсобытия, а затем вызовет carousel() для каждого элемента с carouselклассом:

window.addEventListener('load', () => {
  var carousels = document.querySelectorAll('.carousel');

  for (var i = 0; i < carousels.length; i++) {
    carousel(carousels[i]);
  }
});

carousel() Выполняет три основные задачи:

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

 

Перед рассмотрением кода настройки преобразования я рассмотрю некоторые ключевые переменные и как они инициализируются на основе конфигурации экземпляра:

var
  figure = root.querySelector('figure'),
  images = figure.children,
  n = images.length,
  gap = root.dataset.gap || 0,
  bfc = 'bfc' in root.dataset
;

 

Количество изображений (n) инициализируется в зависимости от количества дочерних элементов <figure> элемента. Разделение между слайдами (gap), инициализируется из data-gap атрибута HTML5 , если установлено. Флаг видимости обратной стороны ( bfc) читается с использованием API набора данных HTML5. Это будет использоваться позже, чтобы определить, должны ли изображения на обратной стороне карусели быть видимыми или нет.

 

Настройка преобразований CSS

Код, который устанавливает свойства, связанные с преобразованиями CSS, инкапсулируется в setupCarousel(). Эта вложенная функция принимает два аргумента. Первое - количество элементов в карусели, то есть nвведенная выше переменная. Второй параметр s- длина стороны многоугольника карусели. Как я упоминал ранее, это равно ширине изображений, поэтому можно прочитать текущую ширину одного из них getComputedStyle():

setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));

 

Таким образом, ширина изображения может быть задана с помощью значений процентов.

Чтобы сохранить отзыв карусели, я регистрирую слушателя для resize события окна, который setupCarousel() снова вызывает изменения (в конечном итоге) измененных размеров изображений:

window.addEventListener('resize', () => { 
  setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));
});

 

Для простоты я не разбираюсь в слушателе изменения размера.

Первое, что setupCarousel() нужно сделать, - вычислить apothem многоугольника, используя переданные параметры и ранее обсуждавшуюся формулу:

apothem = s / (2 * Math.tan(Math.PI / n));

 

Затем это значение используется для изменения начала преобразования фигурного элемента для получения новой оси вращения карусели: 

figure.style.transformOrigin = `50% 50% ${-apothem}px`;

 

Затем применяются стили для изображений:

for (var i = 0; i < n; i++) {
  images[i].style.padding = `${gap}px`;
}

for (i = 1; i < n; i++) {
  images[i].style.transformOrigin = `50% 50% ${- apothem}px`;
  images[i].style.transform = `rotateY(${i * theta}rad)`;
}

if (bfc) {
  for (i = 0; i < n; i++) {
    images[i].style.backfaceVisibility = 'hidden';
  }
}

Первый цикл присваивает пробел для пространства между элементами карусели. Второй цикл устанавливает 3D-преобразования. Последний цикл обрабатывает обратные грани, если соответствующий флаг был указан в конфигурации карусели.

Наконец, rotateCarousel() вызывается, чтобы перенести текущее изображение на передний план. Это небольшая вспомогательная функция, которая, учитывая индекс отображаемого изображения, поворачивает фигурный элемент по оси Y для перемещения целевого изображения вперед. Он также используется навигационным кодом для перехода туда и обратно:

 

function rotateCarousel(imageIndex) {
  figure.style.transform = `rotateY(${imageIndex * -theta}rad)`;
}

 

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

Дорогой web-мастер.
Для меня очень важна обратная связь от Вас в виде лайков или рейтинга.
Пожалуйста оцените эту публикацию или поставь лайк за старание.
Статья подготовлена для Вас сайтом kisameev.ru
Перевел: Кисамеев Дмитрий
Статью просмотрели: 3419
Понравилось:
Поделится ссылкой
Теги:
дмитрий
очень жаль, что сие не работает в ie
Сергей
Ели нашел кнопку "исходники"

Для вставки кода используйте комментарии от ВК или FB:

Похожие публикации

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

Нашли баг или ошибку?

Выдели текст
мышкой и нажми:
Нашел ошибку