Библиотека для анимации Anime.js
01-02-2021Время чтения ~ 7 мин.Alpine.js, jQuery и JavaScript 4710
WEB-анимация достаточно специфичная область и не каждый разработчик с ней сталкивается. Если не рассматривать создание сложной «мультипликации», то современная анимация на сайтах выполняется с помощью CSS. Возможности здесь большие, поэтому сейчас доступно множество самых разных библиотек, предлагающих готовые css-классы.
У такого подхода есть несколько ограничений. Поскольку сама анимация жестко прописывается в виде класса, то для изменений требуется новая sass-компиляция или новый css-класс. Если, скажем, мы описываем анимацию цвета, то для 10 вариантов потребуется как минимум 10 новых классов. Аналогичная ситуация с размерами, отступами и т.д. То есть анимация в виде css-классов больше рассчитана на какие-то «фиксированные»/«типовые» эффекты.
Я уже много писал про Alpine.js как замену jQuery. Но у jQuery есть специальный метод .animate()
, с помощью которого можно создать анимацию. Она довольно ограниченная, но для многих задач её хватает. В Alpine.js такого нет, поэтому я нашёл отдельную библиотеку Anime.js, с помощью которой можно создавать совершенно потрясающие вещи.
Anime.js прекрасно дополняет Альпину. Примеры я будут показывать именно в такой связке, поскольку это не только здорово упрощает js-код, но и позволяет задействовать lazy-загрузку js-файлов в конце BODY.
К достоинствам Anime.js можно отнести и очень малый размер js-файла — всего 17Кб (это примерно 7Кб трафика).
Рассмотрим первый простой пример - анимация кнопки:
<button x-data @click="anime({ targets: $el, duration: 1000, easing: 'linear', rotate: [360, 0] });" class="button button1">Animate</button>
Это компонент Альпины, где мы цепляемся к клику по кнопке. Функция anime()
принадлежит Anime.js — собственно именно она и выполняет анимацию. Магическая переменная Альпины $el
указывает на текущий DOM-элемент. То есть этот код можно трактовать как «сделай анимацию текущего элемента по клику».
Функция anime()
принимает ряд параметров.
- targets — это элемент, который будет анимироваться
- duration — время анимации в милисекундах
- easing — «временная» функция анимации, здесь анимация линейная
- rotate — это css-свойство, которое будет анимироваться, в данном случае это
transform: rotate()
В течение 1 секунды кнопка вначале развернётся на 360 градусов, а потом вернётся к нулевому положению. То есть значения css-свойства можно записывать как в виде одного числа (это будет одно действие), так и в виде массива.
<button x-data @click="anime({ targets: $el, duration: 1000, easing: 'easeInOutQuad', rotate: '+=720' });" class="button button1">Animate</button>
В этом примере анимация устанавливает свойство rotate
в 720 градусов к предыдущему значению.
Если указать просто720
, то повторное нажатие кнопки уже не изменит это значение, поскольку оно уже установлено. Но если мы укажем[720, 0]
, то анимация вернётся в начальную точку. Либо использовать функцию сброса анимации перед её началом.
Рассмотрим более сложный пример:
<div x-data @click="anime({ targets: $el, duration: 3000, rotate: [360, 60, 30, 15, 0, -3, -7, -30, 0], easing: 'linear', complete: function () { $el.classList.toggle('bg-green500'); $el.classList.toggle('bg-blue500'); }, });" class="w200px h200px bg-green500"></div>
Здесь вращение, которое задаётся в виде массива значений, а также функция complete()
, которая срабатывает после завершении анимации. Заметьте, что в начале цвет квадрата зелёный, а потом меняется на синий.
Anime.js позволяет анимировать любое css-свойство (в отличие от jQuery). Вот такой пример:
<div x-data @click="play2($el)" class="w200px h200px bg-red500"></div> <script> function play2(el) { let an = anime.timeline({ targets: el, duration: 700, easing: 'linear', }); an .add({ backgroundColor: '#FFFF00', scale: 1.1, borderRadius: ['0%', '50%'], }) .add({ backgroundColor: '#00FF00', scale: .9, }) .add({ backgroundColor: '#00FFFF', scale: 1.3, }) .add({ backgroundColor: '#0000FF', scale: 1, opacity: .5, }) .add({ backgroundColor: '#FF0000', scale: 1.2, }) .add({ scale: 1, borderRadius: ['50%', '0%'], }) } </script>
Функцию анимации я вынес отдельно, чтобы не загромождать HTML. Функция timeline()
задаёт базовую настройку анимации. А функции add()
задают timeline — то есть вначале будет выполнена первая анимация, потом вторая и т.д.
Во всех примерах анимация применяется к текущему элементу. Но, что делать, если анимировать нужно сразу несколько? Здесь опять же на помощь приходит «магия» Alpine.js: используем $refs
.
Прямая - это убежавшая точка
<div x-data @click=" anime({ targets: $refs.ht, duration: 700, easing: 'linear', translateY: [20, 0 -10, 0], translateX: [0, 15], }); anime({ targets: $refs.bt, duration: 500, delay: 300, easing: 'linear', translateY: [0, -7], width: [0, '100%'], height: [0, 14], backgroundColor: ['#fff', '#FACC60'], borderRadius: [0, 5], });"> <h1 x-ref="ht" class="mar0 pos-relative z-index1 t-red t-impact">Прямая - это убежавшая точка</h1> <div x-ref="bt"></div> </div>
Здесь запускается анимация для $refs.ht
и $refs.bt
с той разницей, что для второго элемента дополнительно прописана задержка в параметре delay
. Но идея в том, что анимировать можно любые элементы по любым js-событиям.
Попробуем теперь повторить некоторые jQuery-эффекты. Самый простой это fadeOut — исчезновение через прозрачность.
<div x-data @click="anime({ targets: $el, duration: 1000, opacity: 0, easing: 'linear', });" class="t200 bg-green">Текст текст fadeOut</div>
Теперь эффект slideUp — когда элемент смещается вверх. Здесь нам нужно уже задать дополнительный контейнер, который скрывает выходящее за его границы содержимое. Сама анимация применяется к css-свойству top
— мы смещаем элемент вверх (отрицательное число) на всю его высоту.
<div class="overflow-hidden"> <div x-data @click="anime({ targets: $el, duration: 1000, top: -$el.clientHeight, easing: 'linear', });" class="t200 bg-green pos-relative">Текст текст slideUp</div> </div>
Аналогично можно сделать slideDown, где тот же алгоритм, только top
увеличивается.
<div class="overflow-hidden"> <div x-data @click="anime({ targets: $el, duration: 1000, top: $el.clientHeight, easing: 'linear', });" class="t200 bg-green pos-relative">Текст текст slideDown</div> </div>
В этих примерах после анимации блок всё равно занимает исходное место. Чтобы его спрятать можно ещё анимировать свойство height
и opacity
для красоты.
<div class="overflow-hidden"> <div x-data @click="anime({ targets: $el, duration: 1000, opacity: 0, top: -$el.clientHeight, height: 0, easing: 'linear' });" class="t200 bg-green pos-relative">Текст текст slideUp + fadeOut + height</div> </div>
Либо можно высоту не трогать, а потом её уменьшить до нуля в complete
:
<div class="overflow-hidden"> <div x-data @click="anime({ targets: $el, duration: 1000, opacity: 0, top: -$el.clientHeight, easing: 'linear', complete: function () { $el.style.height = 0; }, });" class="t200 bg-green pos-relative">Текст текст slideUp + fadeOut + height</div>
Минус такого подхода в том, что возникает «скачок» отображения нижнего блока. Хотя, можно добавить ещё одну анимацию после исчезновения блока, анимировать его высоту до нуля.
Итого
Anime.js я стал использовать не так давно, когда стала задача анимировать один из компонентов шапки в MF. Я долго боролся с этой задачкой через привычные css-классы, но там были сложные условия (несколько положений по onscroll), поэтому решил попробовать Anime.js и был приятно удивлён. Анимацией я никогда не занимался, но с Anime.js это оказалось очень просто.
Я рекомендую заглянуть в документацию этой библиотеки, где приведено «море» рабочих примеров, демонстрирующих её возможности. Сделано, конечно, на высоте.
Для меня Anime.js цена ещё и тем, что она фактически закрывает проблему перехода с jQuery на Alpine.js для тех случаев, когда нужна анимация. Даже если сравнивать размеры, то jQuery 3.5 — 89Кб и это примерно в 2 раза больше, чем сумма Alpine + Anime (25Кб и 17Кб). С учётом их потрясающих возможностей можно смело выкидывать jQuery на «свалку истории». :-)