От автора: сегодня мы хотим вам рассказать, как создать веселый слайдер с оригинальной фрагментированной графикой. Все элементы слайдера будут разделены на части с разной анимацией. Анимация на основе библиотеки Pieces – я написал эту библиотеку для создания интересных эффектов.
Анимация для слайд шоу даст следующий конечный результат:
Анимация под управлением anime.js.
Оригинальная идея
Источником вдохновения эффектов послужил Dribbble Shift Animation от Alexander Saunki:
Как только я увидел это, я сразу захотел воссоздать такой эффект на своем недавно запущенном сайте lmgonzalves.com.
Для достижения эффекта я разработал библиотеку Pieces. Она позволяет рисовать и анимировать текст, изображения и пути SVG через прямоугольные фигуры. Без дальнейших разглагольствований давайте узнаем, как использовать библиотеку!
Приступая к работе с Pieces
Подробную документацию о Pieces можно найти в репозитории на Github. Тем не менее, давайте разберем парочку основных концепций, чтобы можно было приступить к использованию этой библиотеки.
Предполагаем, что будем рисовать и анимировать изображение. Поэтому изображения будут базовыми элементами сцены.
Изображение, которое мы будем рисовать – item. Оно будет разделено на несколько частей pieces, которые будут иметь разные размеры и положение, исходя из заданных параметров. Все возможные варианты можно посмотреть в документации на Github.
В уроке мы объясним каждую строчку кода, чтобы вы смогли создать свою анимацию с помощью библиотеки Pieces. Начнем!
Структура HTML
Прежде чем писать JS, давайте разберем HTML слайдера. Разметка простая – у нас есть слайды со своими изображениями и текстом, canvas для анимации и кнопки навигации по слайдеру.
<!-- Pieces Slider --> <div class="pieces-slider"> <!-- Each slide with corresponding image and text --> <div class="pieces-slider__slide"> <img class="pieces-slider__image" src="img/ricardo-gomez-angel-381749.jpg" alt=""> <div class="pieces-slider__text">Ricardo Gomez Angel</div> </div> <div class="pieces-slider__slide"> <img class="pieces-slider__image" src="img/josh-calabrese-527813.jpg" alt=""> <div class="pieces-slider__text">Josh Calabrese</div> </div> <div class="pieces-slider__slide"> <img class="pieces-slider__image" src="img/samuel-zeller-103111.jpg" alt=""> <div class="pieces-slider__text">Samuel Zeller</div> </div> <div class="pieces-slider__slide"> <img class="pieces-slider__image" src="img/sweet-ice-cream-photography-143023.jpg" alt=""> <div class="pieces-slider__text">Sweet Ice Cream</div> </div> <div class="pieces-slider__slide"> <img class="pieces-slider__image" src="img/sticker-mule-199237.jpg" alt=""> <div class="pieces-slider__text">Sticker Mule</div> </div> <!-- Canvas to draw the pieces --> <canvas class="pieces-slider__canvas"></canvas> <!-- Slider buttons: prev and next --> <button class="pieces-slider__button pieces-slider__button--prev">prev</button> <button class="pieces-slider__button pieces-slider__button--next">next</button> </div>
Стилизация слайдера
Для эффектов в слайдшоу понадобится добавить особые стили. Необходимо прятать изображения и текст – мы будем рисовать их заново с помощью библиотеки. Изображения должны быть фолбеком на случай отключения JS. С помощью пары медиа запросов необходимо определить адаптивность слайдера:
.pieces-slider { position: relative; text-align: center; padding: 8rem 0; } .js .pieces-slider { padding: 0; } /* Make all slides absolutes and hide them */ .js .pieces-slider__slide { position: absolute; right: 100%; } /* Define image dimensions and also hide them */ .pieces-slider__image { max-width: 600px; max-height: 400px; } .js .pieces-slider__image { visibility: hidden; } /* Hide the titles */ .js .pieces-slider__text { text-indent: -9999px; } /* Canvas with viewport width and height */ .js .pieces-slider__canvas { position: relative; width: 100vw; height: 100vh; transition: 0.2s opacity; } /* Class for when we resize */ .pieces-slider__canvas--hidden { opacity: 0; transition-duration: 0.3s; } /* Navigation buttons */ .pieces-slider__button { position: absolute; left: 0; top: 50%; width: 100px; height: 100px; margin: -25px 0 0 0; background-color: #5104ab; color: #fff; font-family: inherit; font-weight: bold; border: none; cursor: pointer; transition: 0.1s background-color; } .pieces-slider__button:hover { background: #5f3abf; } .pieces-slider__button--next { left: auto; right: 0; } /* Hide the buttons when no JS */ .no-js .pieces-slider__button { display: none; } /* Media queries with styles for smaller screens */ @media screen and (max-width: 720px) { .pieces-slider__image { max-width: 300px; } } @media screen and (max-width: 55em) { .pieces-slider__canvas { width: 100vw; height: 100vw; } .pieces-slider__button { width: 60px; height: 60px; } }
У нас есть скрытые HTML элементы, заданные для слайдера (кроме кнопок), так как все будет рисоваться в теге canvas.
Использование Pieces для анимации слайдера
Начнем с определения переменных и получения информации о слайдере из DOM:
// Get all images and texts, get the `canvas` element, and save slider length var sliderCanvas = document.querySelector('.pieces-slider__canvas'); var imagesEl = [].slice.call(document.querySelectorAll('.pieces-slider__image')); var textEl = [].slice.call(document.querySelectorAll('.pieces-slider__text')); var slidesLength = imagesEl.length;
Далее необходимо определить переменные-индексы для обработки всех элементов, рисуемых на canvas:
// Define indexes related variables, as we will use indexes to reference items var currentIndex = 0, currentImageIndex, currentTextIndex, currentNumberIndex; var textIndexes = []; var numberIndexes = []; // Update current indexes for image, text and number function updateIndexes() { currentImageIndex = currentIndex * 3; currentTextIndex = currentImageIndex + 1; currentNumberIndex = currentImageIndex + 2; } updateIndexes();
Перейдем к определению вариантов для всех типов элементов (изображения, текст, номер и кнопка). Весь справочник можно найти в документации Pieces. Ниже представлен детальный разбор всех опций, используемых для отрисовки изображений:
// Options for images var imageOptions = { angle: 45, // rotate item pieces 45deg extraSpacing: {extraX: 100, extraY: 200}, // this extra spacing is needed to cover all the item, because angle != 0 piecesWidth: function() { return Pieces.random(50, 200); }, // every piece will have a random width between 50px and 200px ty: function() { return Pieces.random(-400, 400); } // every piece will be translated in the Y axis a random distance between -400px and 400px };
Точно так же мы определим опции для других типов элементов. Смотрите комментарии, чтобы понять используемые свойства:
// Опции для текста var textOptions = { color: 'white', backgroundColor: '#0066CC', fontSize: function() { return windowWidth > 720 ? 50 : 30; }, padding: '15 20 10 20', angle: -45, extraSpacing: {extraX: 0, extraY: 300}, piecesWidth: function() { return Pieces.random(50, 200); }, ty: function() { return Pieces.random(-200, 200); }, translate: function() { if (windowWidth > 1120) return {translateX: 200, translateY: 200}; if (windowWidth > 720) return {translateX: 0, translateY: 200}; return {translateX: 0, translateY: 100}; } }; // Опции для чисел var numberOptions = { color: 'white', backgroundColor: '#0066CC', backgroundRadius: 300, fontSize: function() { return windowWidth > 720 ? 100 : 50; }, padding: function() { return windowWidth > 720 ? '18 35 10 38' : '18 25 10 28'; }, angle: 0, piecesSpacing: 2, extraSpacing: {extraX: 10, extraY: 10}, piecesWidth: 35, ty: function() { return Pieces.random(-200, 200); }, translate: function() { if (windowWidth > 1120) return {translateX: -340, translateY: -180}; if (windowWidth > 720) return {translateX: -240, translateY: -180}; return {translateX: -140, translateY: -100}; } };
Теперь у нас есть все опции для всех типов элементов. Соберем все и передадим в библиотеку Pieces!
// Build the array of items to draw using Pieces var items = []; var imagesReady = 0; for (var i = 0; i < slidesLength; i++) { // Wait for all images to load before initializing the slider and event listeners var slideImage = new Image(); slideImage.onload = function() { if (++imagesReady == slidesLength) { initSlider(); initEvents(); } }; // Push all elements for each slide with the corresponding options items.push({type: 'image', value: imagesEl, options: imageOptions}); items.push({type: 'text', value: textEl.innerText, options: textOptions}); items.push({type: 'text', value: i + 1, options: numberOptions}); // Save indexes textIndexes.push(i * 3 + 1); numberIndexes.push(i * 3 + 2); // Set image src slideImage.src = imagesEl.src; }
Помимо создания массива элементов в коде сверху мы определили простой механизм вызова функции initSlider только, когда все изображения загружены. Это очень важно, так как мы не можем использовать Pieces для отрисовки недоступных изображений.
Пока что мы еще ничего не рисуем, но уже готовы. Как создается новый объект Pieces:
// Save the new Pieces instance piecesSlider = new Pieces({ canvas: sliderCanvas, // CSS selector to get the canvas items: items, // the Array of items we've built before x: 'centerAll', // center all items in the X axis y: 'centerAll', // center all items in the Y axis piecesSpacing: 1, // default spacing between pieces fontFamily: ["'Helvetica Neue', sans-serif"], animation: { // animation options to use in any operation duration: function() { return Pieces.random(1000, 2000); }, easing: 'easeOutQuint' }, debug: false // set `debug: true` to enable debug mode });
Все элементы и части готовы к анимации. Они созданы, но скрыты по умолчанию. Давайте посмотрим, как показать первый слайд и привязать к нему анимацию:
// Animate all numbers to rotate clockwise indefinitely piecesSlider.animateItems({ items: numberIndexes, duration: 20000, angle: 360, loop: true }); // Show current items: image, text and number showItems();
Для показа и скрытия текущих элементов необходимо вызвать функции showItems и hideItems соответственно. Функции:
// Show current items: image, text and number function showItems() { // Show image pieces piecesSlider.showPieces({items: currentImageIndex, ignore: ['tx'], singly: true, update: (anim) => { // Stop the pieces animation at 60%, and run a new indefinitely animation of `ty` for each piece if (anim.progress > 60) { var piece = anim.animatables[0].target; var ty = piece.ty; anime.remove(piece); anime({ targets: piece, ty: piece.h_ty < 300 ? [{value: ty + 10, duration: 1000}, {value: ty - 10, duration: 2000}, {value: ty, duration: 1000}] : [{value: ty - 10, duration: 1000}, {value: ty + 10, duration: 2000}, {value: ty, duration: 1000}], duration: 2000, easing: 'linear', loop: true }); } }}); // Show pieces for text and number, using alternate `ty` values piecesSlider.showPieces({items: currentTextIndex}); piecesSlider.showPieces({items: currentNumberIndex, ty: function(p, i) { return p.s_ty - [-3, 3]; }}); } // Hide current items: image, text and number function hideItems() { piecesSlider.hidePieces({items: [currentImageIndex, currentTextIndex, currentNumberIndex]}); }
Для перемещения между слайдами мы написали эти функции:
// Select the prev slide: hide current items, update indexes, and show the new current item function prevItem() { hideItems(); currentIndex = currentIndex > 0 ? currentIndex - 1 : slidesLength - 1; updateIndexes(); showItems(); } // Select the next slide: hide current items, update indexes, and show the new current item function nextItem() { hideItems(); currentIndex = currentIndex < slidesLength - 1 ? currentIndex + 1 : 0; updateIndexes(); showItems(); }
Эти функции необходимо вызывать по клику на кнопки навигации или при нажатии стрелок на клавиатуре (лево и право):
// Select prev or next slide using buttons prevButtonEl.addEventListener('click', prevSlide); nextButtonEl.addEventListener('click', nextSlide); // Select prev or next slide using arrow keys document.addEventListener('keydown', function (e) { if (e.keyCode == 37) { // left prevSlide(); } else if (e.keyCode == 39) { // right nextSlide(); } });
Почти закончили Осталось реализовать адаптивность. Необходимо следить за событием resize, сохраняю текущую width окна и пересоздавая слайдер:
// Handle `resize` event window.addEventListener('resize', resizeStart); var initial = true, hideTimer, resizeTimer; // User starts resizing, so wait 300 ms before reinitialize the slider function resizeStart() { if (initial) { initial = false; if (hideTimer) clearTimeout(hideTimer); sliderCanvas.classList.add('pieces-slider__canvas--hidden'); } if (resizeTimer) clearTimeout(resizeTimer); resizeTimer = setTimeout(resizeEnd, 300); } // User ends resizing, then reinitialize the slider function resizeEnd() { initial = true; windowWidth = window.innerWidth; initSlider(); hideTimer = setTimeout(() => { sliderCanvas.classList.remove('pieces-slider__canvas--hidden'); }, 500); }
Теперь все! Надеемся, этот урок был полезен, и вы нашли вдохновение!
Автор: Luis Manuel
Источник: https://tympanus.net/
Редакция: Команда webformyself.