Кейс модификации Tilda: создание слайдера с анимированными карточками отзывов для сайта

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

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

Во время реализации я опирался на референс с Dribble, который мне предоставил клиент:

Референс слайдера, который предоставил клиент (Источник: Outcrowd)

Зачем делать ещё один слайдер?

Я расскажу о своем подходе к разработке модификаций для Тильды на примере слайдера и покажу как можно интегрировать и расширить уже существующее решение на примере библиотеки Swiper.

Цель этой статьи — поделиться моим опытом разработки модификаций для Тильды и осветить некоторые особенности, которые могут возникнуть в ходе разработки нового функционала.

Содержание

1. Выбор библиотеки для слайдера: почему Swiper

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

Из-за этого было решено взять за основу популярную библиотеку Swiper (почти 40 тыс. звезд на GitHub) для создания слайдеров и реализовать собственную анимацию на базе нее.

Swiper имеет множество вариантов слайдеров из коробки, в случае необходимости его можно расширить с помощью плагинов (Источник: Swiper)

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

2. Преобразование Zero-block в интерактивный слайдер

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

Исходный блок с карточками отзывов
Исходный блок с карточками отзывов

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

  1. Добавить контейнер для слайдера в зеро блок
  2. Оформить карточки, которые будут служить слайдами
  3. Инициализация слайдера
  4. Интеграция слайдера с существующей версткой Tilda бы

Далее опишу каждое из действий подробнее.

2.1. Добавление контейнера для слайдера

Первым делом, в редакторе зеро блока с карточками добавим элемент с HTML-кодом:

<div class="swiper"> <div class="swiper-wrapper"> </div> </div>
Кейс модификации Tilda: создание слайдера с анимированными карточками отзывов для сайта

Размеры и положение этого элемента будут служить контейнером для слайдера. Таким образом, можно будет задать ширину контейнера для каждого брейкпоинта в зеро блоке.

Класс задавать этому элементу не нужно, потому что вставляемый HTML-код с классом swiperи так будет находиться внутри зеро блока.

2.2. Оформление групп в виде объектов

Чтобы карточки можно было использовать как слайды, их можно сгруппировать в виде объекта (Object). Это укажет Тильде, что каждую группу нужно оформить в виде отдельного DIV элемента, с которым можно будет взаимодействовать с помощью JavaScript.

Кейс модификации Tilda: создание слайдера с анимированными карточками отзывов для сайта

Чтобы скрипт мог найти карточки, я задал CSS класс card-text для элементов с текстом отзыва. Благодаря этому классу, в коде карточки можно найти с помощью querySelectorAll:

const block = document.querySelector('.uc-review-cards') block.querySelectorAll('.card-text')

Хочу отметить, что я нахожу зеро блок также через класс uc-review-cards, в отличие от того, как это обычно делают в бесплатных модификациях по rec id: #rec123456789. Я так делаю потому, что при поиске через класс удобно переносить блок между страницами или переключаться между разными версиями блока — не нужно каждый раз менять rec id в коде.

2.3. Инициализация слайдера

После того как элементы с карточками были найдены, их нужно переместить в контейнер слайдера Swiper и добавить класс swiper-slide:

const block = document.querySelector('.uc-review-cards') const sliderWrapper = block.querySelector('.swiper-wrapper') block.querySelectorAll('.card-text').forEach(el => { const slide = el.closest('.tn-group') slide.classList.add('swiper-slide') sliderWrapper.append(slide) })

Здесь поиск происходит по родительскому элементу с классом tn-group, он доступен из-за того, что ранее был выбран тип группы Object.

Теперь можно инициализировать слайдер:

document.addEventListener('DOMContentLoaded', () => { const block = document.querySelector('.uc-review-cards') const sliderWrapper = block.querySelector('.swiper-wrapper') block.querySelectorAll('.card-text').forEach(el => { const slide = el.closest('.tn-group') slide.classList.add('swiper-slide') sliderWrapper.append(slide) }) const swiper = new Swiper(block.querySelector('.swiper'), { loop: true, slidesPerView: 3, grabCursor: true, centeredSlides: true, }) })
А вот результат...
А вот результат...

Как видно, с первого раза слайдер завести не получилось. На самом деле он работает, но из-за особенностей верстки в Tilda он не отображается. Это можно исправить, модифицировав CSS стили.

2.4. Интеграция слайдера с существующей версткой Tilda

Прична, по которой слайдер не показывается вызван конфликтом со стилями Tilda. Из-за этого Swiper не может корректно рассчитать размер контейнера. Чтобы это исправить, нужно добавить дополнительные CSS стили:

.uc-review-cards .swiper { width: 100%; position: absolute; overflow: visible; /* Чтобы карточки было видно за пределами контейнера */ } .uc-review-cards .swiper-slide { position: initial !important; }

Данный CSS масштабирует контейнер Swiper с учетом вложенности в зеро блок Тильды. В результате получим базовый слайдер с горизонтальной прокруткой:

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

У Swiper есть различные заготовки для переходов и одна из них позволяет гибко настраивать переходы между слайдами. Она называется creative effect, которую мы рассмотрим далее.

3. Первая попытка: анимация с помощью effect creative

С помощью effect creative можно задать смещение, поворот, прозрачность и масштаб следующего и предыдущего слайдов:

Примеры effect creative с различными параметрами (Источник: Swiper)

Определим собственные параметры для effect creative таким образом, чтобы это было похоже на эффект из референса:

const swiper = new Swiper(block.querySelector('.swiper'), { loop: true, grabCursor: true, centeredSlides: true, slidesPerView: 3, // Укажем эффект 'creative' и зададим правила анимации effect: 'creative', creativeEffect: { prev: { translate: [-150, 20, -150], rotate: [0, 0, -30], origin: 'center bottom', }, next: { translate: [150, 65, -150], rotate: [0, 0, 30], origin: 'center bottom', }, }, })

В результате получим следующий эффект:

Как видно, эффект работает, однако только для трех карточек.

Недостатком effect creative является то, что он работает только для 3-х слайдов (следующий, предыдущий и текущий). Применить данный эффект к большему количеству карточек не получится.

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

4. Разработка собственного модуля Swiper с уникальной анимацией карточек

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

const MySwiperModule = ({ swiper, params, on }) => { // Код, реализующий уникальную анимацию } const swiper = new Swiper(block.querySelector('.swiper'), { // Конфигурация слайдера modules: [ MySwiperModule ], // Подключение собственного модуля })

Определение функции для анимации слайдов

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

К сожалению, Swiper не предоставляет обработчика события при обновлении слайдера. У него есть событие progress, которое срабатывает во время перетаскивания слайдера. Однако оно не срабатывает, когда происходит выравнивание по центру при окончании перетаскивания.

Вместо этого, можно использовать функцию Web API requestAnimationFrame, которая срабатывает на каждом кадре отрисовки изображения в браузере и с помощью которой можно реализовать плавные анимации с частотой 60 Hz и выше, в зависимости от устройства. Подробнее про то, как работает requestAnimationFrame можно прочитать на MDN.

В итоге получим заготовку для анимации слайдов:

const MySwiperModule = ({ swiper, params, on }) => { let animationFrame const animate = () => { swiper.slides.forEach((slide, index) => { // Логика для анимации каждого слайда }) animationFrame = requestAnimationFrame(animate) } animationFrame = requestAnimationFrame(animate) }

Если запустить слайдер сейчас, то никакой анимации кроме перемещения, очевидно, не будет:

Swiper отвечает за логику перемещения карточек при перетаскивании, но не анимирует карточки

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

5. Анимация карточек с помощью интерполяции

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

Общий принцип работы линейной интерполяции — расчет значений между точками (x0, y0) и (x1, y1)
Общий принцип работы линейной интерполяции — расчет значений между точками (x0, y0) и (x1, y1)

Обычно для удобства работы, значение интерполяции во время нормализуется и считается на интервале от 0.0 до 1.0.

Можно представить координаты карточек в координатной сетке слайдера по аналогии с предыдущим рисунком:

Кейс модификации Tilda: создание слайдера с анимированными карточками отзывов для сайта

В данном случае левое значение будет равно 0, а правое — 1. Чтобы посчитать промежуточные значения координат, нужно знать ширину контейнера слайдера, его положение на странице, а также размеры и положение карточки, для которой рассчитывается интерполяция.

На самом деле, для нашего случая нужен немного смещенный диапазон: от -1 до 1. Это позволит поворачивать и смещать карточки как влево (-1), так и вправо (1).

const animate = () => { // Размеры и положение контейнера слайдера const swiperRect = swiper.el.getBoundingClientRect() const centerX = swiperRect.x + swiperRect.width / 2 swiper.slides.forEach((slide, index) => { // Размеры и положение одного слайда (карточки) const rect = slide.getBoundingClientRect() // Абсолютные координаты середины карточки const slideX = rect.x + rect.width / 2 // Размер конкретного слайдера, который задан Swiper-ом const slideSize = swiper.slidesSizesGrid[index] // Количество карточек в одной половине слайдера const numberOfSlidesOnOneSide = swiper.slidesPerViewDynamic() - 1 const rawInterpolation = (slideX - centerX) / slideSize / Math.max(1, numberOfSlidesOnOneSide / 2) console.log(rawInterpolation) }) }

В результате получим значения "сырой" (raw) интерполяции, которая может выходить за пределы [-1, 1]. Главное здесь то, что по данной формуле рассчитываемое значение всегда симметрично середины слайдера. Вот пример значений rawInterpolation для 10 слайдов:

[-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] ^^^ | Середина слайдера |

Ограничить значения за пределами слайдера легко, воспользовавшись математической функцией clamp:

const clampedInterpolation = Math.min(1, Math.max(-1, rawInterpolation))

Можно посмотреть как работает полученное значение, задав трансформацию карточкам. Например, анимируем смещение по вертикали и угол наклона:

slide.style.transform = `translateY(${-clampedInterpolation * 100}px) rotate(${90 * clampedInterpolation}deg)`
Значения смещения по вертикали и угол наклона симметричны относительно середины слайдера

Сглаживание анимации интерполируемых параметров

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

Определим два значения интерполяции — линейное и кубическое:

const linear = clampedInterpolation const cubic = Math.pow(linear, 3)

На изображении ниже представлена разница между ними (только в нашем случае у нас всего две точки, а не множество точек, как на рисунке ниже, но принцип сглаживания один и тот же).

Линейная и кубическая интерполяция. Во втором случае значение возведено в куб (третью степень). Источник: <a href="https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fwww.linkedin.com%2Fpulse%2Fmore-than-linear-interpolation-python-alvaro-carnielo-e-silva&postId=1624724" rel="nofollow noreferrer noopener" target="_blank">Alvaro Carnielo e Silva</a>
Линейная и кубическая интерполяция. Во втором случае значение возведено в куб (третью степень). Источник: Alvaro Carnielo e Silva

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

slide.style.transform = `translateX(${linear * -200}px) translateY(${Math.abs(cubic) * 100}px) rotate(${45 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})` slide.style.opacity = Math.abs(rawInterpolation) > 1 ? 0 : 'initial' slide.style.transformOrigin = 'center bottom'

Финальные штрихи

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

if (document.body.clientWidth >= 960) { slide.style.transform = `translateX(${linear * -200}px) translateY(${Math.abs(cubic) * 100}px) rotate(${45 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})` slide.style.transformOrigin = 'center bottom' slide.style.opacity = Math.abs(rawInterpolation) > 1 ? 0 : 'initial' } else if (document.body.clientWidth >= 640) { slide.style.transform = `translateX(${linear * -300}px) translateY(${Math.abs(cubic) * 50}px) rotate(${22.5 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})` slide.style.transformOrigin = 'center bottom' slide.style.opacity = Math.abs(rawInterpolation) > 1 ? 0 : 'initial' } else if (document.body.clientWidth >= 480) { slide.style.transform = `translateX(${linear * -125}px) translateY(${Math.abs(cubic) * 50}px) rotate(${22.5 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})` slide.style.transformOrigin = 'center' slide.style.opacity = 'initial' } else { slide.style.transform = `translateX(${linear * -100}px) translateY(${Math.abs(cubic) * 50}px) rotate(${22.5 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})` slide.style.transformOrigin = 'center' slide.style.opacity = 'initial' }

6. Конечный результат

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

Конечная реализация слайдера с всплывающим окном

В общей сложности разработка подобного слайдера с учетом подводных каменей Swiper, связанных с прокруткой по клику и отображением карточек в мобильной версии заняла порядка 12 часов.

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

Если вам нужна разработка уникальной модификации или интеграции Tilda со сторонним сервисом — больше кейсов и мои контакты доступны по ссылке: codly.cc
Также в своем блоге «Код без тайн» я периодически пишу о веб-разработке, информатике и других сферах, которые меня вдохновляют (искусственный интеллект, дизайн и многое другое).
22
1 комментарий

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

Ответить