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

Когда я начал изучать эту тему, мне не нужна была трехмерная карусель, но я больше интересовался техническими деталями ее реализации. Разумеется, основные используемые методы, конечно же, относятся к модулю 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)`;
    }

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

    Transform CSS3 к фоновым изображениям. Rotate вращение фона

    С помощью transform свойства CSS3 возможно масштабирование, перекос и поворот любого элемента. Это свойство поддерживается во всех современных браузерах без префиксов. Если вам необходимо поддерживать браузер Blackberry и браузер UC для Android, вам нужно использовать -webkit-префикс, например:

    #myelement {
      -webkit-transform: rotate(30deg);
      transform: rotate(30deg);
    }

    Однако это вращает весь элемент и все его содержимое, границу и фоновое изображение, текст и т.д.. Что делать, если вы хотите только повернуть только фоновое изображение? Или что, если вы хотите, чтобы фон оставался фиксированным, когда элемент вращается?

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

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

    Transform только фона

    Элемент контейнера может иметь любые применяемые стили, но он должен быть установлен position: relative так, поскольку наш псевдоэлемент будет размещен внутри него. Вы также должны установить, overflow: hidden если вы не удовлетворены тем, что фон вылился за пределы контейнера.

    #myelement {
      position: relative;
      overflow: hidden;
    }

    Теперь мы можем создать псевдоэлемент со свойством position: absolute с преобразованным фоном. Для z-index установлено значение -1, чтобы убедиться, что оно отображается под содержимым контейнера.

    #myelement:before {
      content: "";
      position: absolute;
      width: 200%;
      height: 200%;
      top: -50%;
      left: -50%;
      z-index: -1;
      background: url(background.png) 0 0 repeat;
      -webkit-transform: rotate(30deg);
      transform: rotate(30deg);
    }

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

    Фиксированый фон на преобразованном элементе

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

    #myelement {
      position: relative;
      overflow: hidden;
      -webkit-transform: rotate(30deg);
      transform: rotate(30deg);
    }
    #myelement:before {
      content: "";
      position: absolute;
      width: 200%;
      height: 200%;
      top: -50%;
      left: -50%;
      z-index: -1;
      background: url(background.png) 0 0 repeat;
      -webkit-transform: rotate(-30deg);
      transform: rotate(-30deg);
    }

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

    Эффекты работают во всех основных браузерах, а в Edge и Internet Explorer — до версии 9. IE8 не будет показывать никаких преобразований, но фон все равно появляется.

    IE6 и 7 не поддерживают псевдоэлементы, поэтому фон исчезает.Однако для поддержки этих древних браузеров вы можете применить фоновое изображение к контейнеру, а затем установить его «none» с помощью расширенного селектора или условного CSS.

    Заливка элемента от точки наведения курсора

    В просторах интернета наткнулся на сайт, где реализован интересный и довольно простой эффект наведения на кнопку.

    Живой пример:

    Суть

    1. Наводим курсор на кнопку
    2. Высчитываем положение курсора относительно кнопки
    3. Исходя из координатов курсора перемещается элемент
    4. Видим эффект заполнения кнопки

    HTML разметка

    Я подключил библиотеку jquey, потому что мне так удобнее.

    <a href="#" class="btn">
    	<i class="fa fa-fire-extinguisher" aria-hidden="true"></i>
    	<span class="btn_text">Наведите мышкой</span>
    	<span class="btn_hover"></span>
    </a>
     
    <a href="#" class="btn">
    	<i class="fa fa-check" aria-hidden="true"></i>
    	<span class="btn_text">В разные места</span>
    	<span class="btn_hover"></span>
    </a>
     
    <a href="#" class="btn">
    	<i class="fa fa-heart-o" aria-hidden="true"></i>
    	<span class="btn_text">И еще разок</span>
    	<span class="btn_hover"></span>
    </a>

    CSS

    .btn
    {
        display: inline-block;
        padding: 12px 55px;
        font-size: 20px;
        line-height: 1.42857143;
        text-align: center;
        color: #fff;
        border-radius: 4px;
        border: 2px solid rgb(255, 255, 255);
        text-decoration: none;
        position: relative;
        overflow: hidden;
    }
     
    .btn:hover .btn_hover
    {
    	width: 1000px;
    	height: 1000px;	
    }
     
    .btn .btn_hover
    {
        position: absolute;
        z-index: -1;
        width: 0;
        height: 0;
        border-radius: 50%;
        background: #50e3c2;
        -webkit-transform: translate(-50%, -50%);
        transform: translate(-50%, -50%);
        transition: width .6s, height .6s, background-color .4s;
    }

    JavaScript

    $(function(){
      $(".btn").mouseover(function(e){
        // положение элемента
        var pos = $(this).offset();
        var elem_left = pos.left;
        var elem_top = pos.top;
        // положение курсора внутри элемента
        var Xinner = e.pageX - elem_left;
        var Yinner = e.pageY - elem_top;
    		$(this).find(".btn_hover").css({
    			"left":Xinner,
    			"top":Yinner
    		})
      });
    });

    Вот и все. Ничего сложного.

    Таким образом мы получили интересный и просто эффект наведения курсора на кнопку под средствами css3 и javascript.

    Кисамеев Логотип
    Не в сети
    Услуги