Библиотека для анимации Anime.js
01-02-2021Время чтения ~ 7 мин.Alpine.js, jQuery и JavaScript 5639
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 на «свалку истории». :-)