Использование API анимаций веб-страниц
API анимаций веб-страниц позволяет создавать анимации и управлять их воспроизведением с помощью JavaScript. Эта статья поможет вам начать работу с интересными демонстрациями и учебными пособиями, в которых участвует Алиса в Стране чудес.
Знакомство с API анимаций веб-страниц
API анимаций веб-страниц открывает доступ к движку анимаций браузера для разработчиков и управления с помощью JavaScript. Этот API был разработан для реализации как анимаций CSS, так и переходов CSS, и оставляет возможность для будущих эффектов анимации. Это один из самых эффективных способов анимировать на веб-странице, позволяющий браузеру производить собственные внутренние оптимизации без использования хакерских методов, принуждения или Window.requestAnimationFrame().
С помощью API анимаций веб-страниц мы можем перенести интерактивные анимации из таблиц стилей в JavaScript, разделив представление и поведение. Нам больше не нужно полагаться на ресурсоёмкие методы DOM, такие как написание свойств CSS и назначение классов элементам для управления направлением воспроизведения. В отличие от чисто декларативного CSS, JavaScript также позволяет нам динамически задавать значения свойств и длительности. Для создания пользовательских библиотек анимаций и интерактивных анимаций API анимаций веб-страниц может стать идеальным инструментом. Давайте посмотрим, на что он способен!
Создание анимаций CSS с помощью API анимаций веб-страниц
Один из самых распространённых способов изучения API анимаций веб-страниц — начать с чего-то, что большинство веб-разработчиков уже использовали: анимаций CSS. Анимации CSS имеют знакомый синтаксис, который удобно использовать для демонстрационных целей.
Вариант с CSS
Вот анимация падения, написанная на CSS, которая показывает Алису, падающую вниз по кроличьей норе, ведущей в Страну чудес (см. полный код на CodePen):
Обратите внимание, что фон перемещается, Алиса вращается, а её цвет меняется с некоторым смещением от её вращения. В этом руководстве мы сосредоточимся только на Алисе. Вот упрощённый CSS, который управляет анимацией Алисы:
#alice {
animation: aliceTumbling infinite 3s linear;
}
@keyframes aliceTumbling {
0% {
color: #000;
transform: rotate(0) translate3D(-50%, -50%, 0);
}
30% {
color: #431236;
}
100% {
color: #000;
transform: rotate(360deg) translate3D(-50%, -50%, 0);
}
}
Это изменяет цвет Алисы и вращение её трансформации в течение 3 секунд с постоянной (линейной) скоростью и повторяется бесконечно. В блоке @keyframes мы видим, что на 30% каждой итерации (примерно через 0,9 секунды) цвет Алисы меняется с чёрного на тёмно-бордовый, а затем возвращается обратно к концу итерации.
Перенос в JavaScript
Теперь давайте попробуем создать ту же анимацию с помощью API анимаций веб-страниц.
Представление ключевых кадров
Сначала нам нужно создать объект ключевых кадров, соответствующий нашему блоку CSS @keyframes:
const aliceTumbling = [
{ transform: "rotate(0) translate3D(-50%, -50%, 0)", color: "#000" },
{ color: "#431236", offset: 0.3 },
{ transform: "rotate(360deg) translate3D(-50%, -50%, 0)", color: "#000" },
];
Здесь мы используем массив, содержащий несколько объектов. Каждый объект представляет ключевой кадр из исходного CSS. Однако, в отличие от CSS, API анимаций веб-страниц не нужно явно указывать процентное соотношение каждого ключевого кадра в анимации. Он автоматически разделит анимацию на равные части в зависимости от количества заданных ключевых кадров. Это означает, что объект ключевых кадров с тремя ключами будет воспроизводить средний кадр на 50% каждой итерации анимации, если не указано иное.
Если мы хотим явно установить смещение ключевого кадра от других, мы можем указать смещение непосредственно в объекте, отделённом запятой от объявления. В приведенном выше примере, чтобы убедиться, что цвет Алисы изменится на 30% (а не на 50%), мы offset: 0.3.
В настоящее время должно быть указано как минимум два ключевых кадра (представляющих начальное и конечное состояния последовательности анимации). Если список ключевых кадров содержит только один элемент, Element.animate() может вызывать NotSupportedError DOMException в некоторых браузерах до их обновления.
Итак, ключевые кадры равномерно распределены по умолчанию, если вы не укажете смещение для определённого ключевого кадра. Удобно, не так ли?
Представление свойств временных параметров
Также нам нужно создать объект свойств временных параметров, соответствующих значениям в анимации Алисы:
const aliceTiming = {
duration: 3000,
iterations: Infinity,
};
Вы заметите несколько отличий от способа представления эквивалентных значений в CSS:
- Во-первых, продолжительность указана в миллисекундах, а не в секундах — 3000, а не 3с. Как и
setTimeout()иWindow.requestAnimationFrame(), API анимаций веб-страниц принимает только миллисекунды. - Другое, что вы заметите, это то, что это
iterations, а неiteration-count.
Примечание: Существует ряд небольших различий в терминологии, используемой в анимациях CSS и анимациях веб-страниц. Например, в анимациях веб-страниц не используется строка "infinite", а вместо неё используется ключевое слово JavaScript Infinity. И вместо timing-function мы используем easing. Мы не указываем здесь значение easing, потому что, в отличие от анимаций CSS, где значение по умолчанию animation-timing-function равно ease, в API анимаций веб-страниц значение по умолчанию для ускорения — linear — что нам и нужно в данном случае.
Объединение элементов
Теперь пришло время объединить их с помощью метода Element.animate():
document.getElementById("alice").animate(aliceTumbling, aliceTiming);
И вуаля: анимация запускается (см. завершенную версию на CodePen).
Метод animate() может быть вызван для любого элемента DOM, который можно анимировать с помощью CSS. И он может быть написан несколькими способами. Вместо создания объектов для ключевых кадров и свойств временных параметров мы можем просто передать их значения напрямую, например так:
document.getElementById("alice").animate(
[
{ transform: "rotate(0) translate3D(-50%, -50%, 0)", color: "#000" },
{ color: "#431236", offset: 0.3 },
{ transform: "rotate(360deg) translate3D(-50%, -50%, 0)", color: "#000" },
],
{
duration: 3000,
iterations: Infinity,
},
);
Более того, если нам нужно указать только длительность анимации, а не её повторения (по умолчанию анимации повторяются один раз), мы можем передать только миллисекунды:
document.getElementById("alice").animate(
[
{ transform: "rotate(0) translate3D(-50%, -50%, 0)", color: "#000" },
{ color: "#431236", offset: 0.3 },
{ transform: "rotate(360deg) translate3D(-50%, -50%, 0)", color: "#000" },
],
3000,
);
Управление воспроизведением с помощью play(), pause(), reverse() и updatePlaybackRate()
Хотя мы можем писать анимации CSS с помощью API анимаций веб-страниц, API действительно становится полезным при управлении воспроизведением анимации. API анимаций веб-страниц предоставляет несколько полезных методов для управления воспроизведением. Давайте рассмотрим приостановку и воспроизведение анимаций в игре "Растущая/Уменьшающаяся Алиса" (см. полный код на CodePen):
В этой игре у Алисы есть анимация, которая делает её размером с маленькую до большой, что мы контролируем с помощью бутылки и пирожного. У обоих есть свои анимации.
Приостановка и воспроизведение анимаций
Мы подробнее поговорим об анимации Алисы позже, но пока давайте посмотрим на анимацию пирожного:
const nommingCake = document
.getElementById("eat-me_sprite")
.animate(
[{ transform: "translateY(0)" }, { transform: "translateY(-80%)" }],
{
fill: "forwards",
easing: "steps(4, end)",
duration: aliceChange.effect.getComputedTiming().duration / 2,
},
);
Метод Element.animate() будет выполнен сразу после вызова. Чтобы предотвратить исчезновение пирожного до того, как пользователь успеет нажать на него, мы сразу же после определения вызываем Animation.pause(), как показано ниже:
nommingCake.pause();
Теперь мы можем использовать метод Animation.play(), чтобы запустить её, когда мы готовы:
nommingCake.play();
В частности, мы хотим связать её с анимацией Алисы, чтобы она становилась больше по мере съедания пирожного. Мы можем достичь этого с помощью следующей функции:
const growAlice = () => {
// Play Alice's animation.
aliceChange.play();
// Play the cake's animation.
nommingCake.play();
};
Когда пользователь удерживает курсор или палец на пирожном на сенсорном экране, мы можем вызвать growAlice для запуска всех анимаций:
cake.addEventListener("mousedown", growAlice, false);
cake.addEventListener("touchstart", growAlice, false);
Другие полезные методы
Помимо приостановки и воспроизведения, мы можем использовать следующие методы анимации:
-
Animation.finish()переходит к концу анимации. -
Animation.cancel()прерывает анимацию и удаляет её эффекты. -
Animation.reverse()устанавливает скорость воспроизведения анимации (Animation.playbackRate) в отрицательное значение, чтобы она воспроизводилась в обратном направлении.
Давайте сначала рассмотрим playbackRate — отрицательная скорость воспроизведения приведет к воспроизведению анимации в обратном порядке. Когда Алиса пьёт из бутылки, она уменьшается в размерах. Это происходит потому, что бутылка изменяет скорость воспроизведения анимации с 1 на -1:
const shrinkAlice = () => {
aliceChange.playbackRate = -1;
aliceChange.play();
};
bottle.addEventListener("mousedown", shrinkAlice, false);
bottle.addEventListener("touchstart", shrinkAlice, false);
В Зазеркалье Алиса попадает в мир, где ей нужно бежать, чтобы оставаться на месте — и бежать вдвое быстрее, чтобы двигаться вперёд! В примере "Забег Красной Королевы" Алиса и Красная Королева бегут, чтобы оставаться на месте (см. полный код на CodePen):
Поскольку маленькие дети устают быстрее, чем автоматизированные шахматные фигуры, Алиса постоянно замедляется. Мы можем сделать это, установив затухание для скорости воспроизведения анимации. Мы используем updatePlaybackRate() вместо непосредственной установки скорости воспроизведения, так как это даёт плавное обновление:
setInterval(() => {
// Make sure the playback rate never falls below .4
if (redQueen_alice.playbackRate > 0.4) {
redQueen_alice.updatePlaybackRate(redQueen_alice.playbackRate * 0.9);
}
}, 3000);
Но подталкивание их, нажатием или нажатием, ускоряет их, умножая скорость воспроизведения:
const goFaster = () => {
redQueen_alice.updatePlaybackRate(redQueen_alice.playbackRate * 1.1);
};
document.addEventListener("click", goFaster);
document.addEventListener("touchstart", goFaster);
Элементы фона также имеют playbackRate , которые изменяются при нажатии или нажатии. Что произойдёт, если вы сделаете Алису и Красную Королеву вдвое быстрее? Что произойдёт, если вы позволите им замедлиться?
Сохранение стилей анимации
При анимации элементов часто требуется сохранить конечное состояние анимации после ее завершения. Один из методов, иногда используемых для этого, — установить режим заполнения анимации на режим заполнения forwards. Однако не рекомендуется использовать режимы заполнения для постоянного сохранения эффекта анимации по двум причинам:
- Браузеру необходимо сохранять состояние анимации в активном режиме, поэтому анимация продолжает потреблять ресурсы, даже если она больше не выполняется. Обратите внимание, что это несколько смягчается благодаря автоматическому удалению анимаций заполнения в браузере.
- Стили, применяемые анимацией, имеют более высокий приоритет в каскаде, чем указанные стили, поэтому их может быть сложно переопределить при необходимости.
Более предпочтительный подход — использовать метод Animation.commitStyles(). Он записывает вычисленные значения текущих стилей анимации в атрибут style целевого элемента, после чего элемент можно перестилизовать в обычном режиме.
Автоматическое удаление анимаций заполнения
Возможно запустить большое количество анимаций на одном элементе. Если они бесконечны (т.е. заполняют вперёд), это может привести к огромному списку анимаций, что может вызвать утечку памяти. По этой причине браузеры автоматически удаляют анимации заполнения после их замены новыми анимациями, если разработчик явно не указывает их сохранить.
Анимации удаляются, когда все из перечисленного ниже истинно:
- Анимация заполняет (ее
fillравноforwardsпри воспроизведении вперёд,backwardsпри воспроизведении назад илиboth). - Анимация завершена. (Обратите внимание, что из-за
fillона всё ещё будет в действии.) - Временная шкала анимации монотонно возрастает. (Это всегда верно для
DocumentTimeline; другие временные шкалы, такие какscroll-timeline, могут выполняться в обратном порядке.) - Анимация не управляется декларативным разметкой, например CSS.
- Каждый стилистический эффект анимации
AnimationEffectпереопределяется другой анимацией, которая также удовлетворяет всем вышеперечисленным условиям. (Как правило, когда две анимации задают один и тот же свойство стиля одного и того же элемента, созданная последней переопределяет другую.)
Первые четыре условия гарантируют, что без вмешательства кода JavaScript эффект анимации никогда не изменится или не завершится. Последнее условие гарантирует, что анимация никогда не повлияет на стиль какого-либо элемента: она полностью заменена.
При автоматическом удалении анимации срабатывает событие remove анимации.
Чтобы предотвратить автоматическое удаление анимации браузером, вызовите метод persist() анимации.
Свойство replaceState анимации будет removed если анимация была удалена, persisted если вы вызвали persist() для анимации или active в противном случае.
Получение информации из анимаций
Представьте другие способы использования playbackRate, например, улучшение доступности для пользователей с вестибулярными расстройствами, позволяя им замедлять анимации на всем сайте. Это невозможно сделать с помощью CSS без перерасчета продолжительности во всех правилах CSS, но с помощью API веб-анимаций мы можем использовать метод Document.getAnimations для перебора каждой анимации на странице и уменьшить их playbackRate вдвое, как показано ниже:
document.getAnimations().forEach((animation) => {
animation.updatePlaybackRate(animation.playbackRate * 0.5);
});
С API веб-анимаций вам нужно изменить всего лишь одно свойство!
Еще одна сложная задача с помощью одних только CSS-анимаций — создание зависимостей от значений, предоставляемых другими анимациями. Например, в примере игры «Растущая и уменьшающаяся Алиса» вы, возможно, заметили нечто странное в продолжительности торта:
document.getElementById("eat-me_sprite").animate([], {
duration: aliceChange.effect.timing.duration / 2,
});
Чтобы понять, что происходит, давайте взглянем на анимацию Алисы:
const aliceChange = document
.getElementById("alice")
.animate(
[
{ transform: "translate(-50%, -50%) scale(.5)" },
{ transform: "translate(-50%, -50%) scale(2)" },
],
{
duration: 8000,
easing: "ease-in-out",
fill: "both",
},
);
Анимация Алисы показывает, как она увеличивается с половины её размера до двойного за 8 секунд. Затем мы приостанавливаем её:
aliceChange.pause();
Если бы мы оставили её приостановленной в начале анимации, она бы начиналась с половины своего полного размера, как будто бы уже выпила всю бутылку! Мы хотим установить «указатель воспроизведения» в середине анимации, чтобы она уже прошла половину пути. Мы можем сделать это, установив её Animation.currentTime на 4 секунды, как показано ниже:
aliceChange.currentTime = 4000;
Но при работе с этой анимацией мы можем многократно менять продолжительность анимации Алисы. Разве не лучше установить её currentTime динамически, чтобы нам не приходилось вносить два изменения одновременно? Мы можем это сделать, ссылаясь на свойство Animation.effect aliceChange, которое возвращает объект, содержащий все детали активных эффектов для Алисы:
aliceChange.currentTime = aliceChange.effect.getComputedTiming().duration / 2;
effect позволяет получить доступ к ключевым кадрам и свойствам тайминга анимации — aliceChange.effect.getComputedTiming() указывает на объект тайминга Алисы — он содержит её duration. Мы можем разделить её продолжительность пополам, чтобы получить середину временной шкалы анимации, установив её в нормальный размер. Теперь мы можем перевернуть и воспроизвести её анимацию в любом направлении, чтобы она становилась меньше или больше!
И мы можем сделать то же самое при установке продолжительности торта и бутылки:
const drinking = document
.getElementById("liquid")
.animate([{ height: "100%" }, { height: "0" }], {
fill: "forwards",
duration: aliceChange.effect.getComputedTiming().duration / 2,
});
drinking.pause();
Теперь все три анимации связаны только с одной продолжительностью, которую мы можем легко изменить из одного места.
Мы также можем использовать API веб-анимаций, чтобы определить текущее время анимации. Игра заканчивается, когда у вас заканчивается торт или бутылка. Какой фрагмент показывается игроку, зависит от того, на какой стадии анимации была Алиса, увеличилась ли она слишком сильно и не может войти в маленькую дверь или стала слишком маленькой и не может достать ключ, чтобы открыть дверь. Мы можем определить, находится ли она на большом или маленьком конце своей анимации, получив её currentTime и разделив её на её activeDuration:
const endGame = () => {
// get Alice's timeline's playhead location
const alicePlayhead = aliceChange.currentTime;
const aliceTimeline = aliceChange.effect.getComputedTiming().activeDuration;
// stops Alice's and other animations
stopPlayingAlice();
// depending on which third it falls into
const aliceHeight = alicePlayhead / aliceTimeline;
if (aliceHeight <= 0.333) {
// Alice got smaller!
// …
} else if (aliceHeight >= 0.666) {
// Alice got bigger!
// …
} else {
// Alice didn't change significantly
// …
}
};
Обратные вызовы и промисы
CSS-анимации и переходы имеют свои собственные обработчики событий, и это также возможно с помощью API веб-анимаций:
-
onfinish— обработчик события завершения и может быть вызван вручную с помощьюfinish(). -
oncancel— обработчик события отмены и может быть вызван с помощьюcancel().
Здесь мы устанавливаем обратные вызовы для торта, бутылки и Алисы, чтобы вызвать функцию endGame:
// When the cake or bottle runs out nommingCake.onfinish = endGame; drinking.onfinish = endGame; // Alice reaches the end of her animation aliceChange.onfinish = endGame;
Еще лучше, API веб-анимаций также предоставляет промис finished, который выполняется, когда анимация завершается, или отбрасывается, если она отменена.
Заключение
Это основные возможности API веб-анимаций. Теперь вы должны быть готовы «погрузиться в кроличью нору» анимации в браузере и готовы к собственным экспериментам с анимацией!
См. также
- Полный набор демо Алисы в Стране чудес на CodePen для игры, форкирования и совместного использования.
- Анимирование, как будто вы просто не заботитесь об Element.animate (2016). Объясняет контекст API веб-анимаций и почему он эффективнее других методов веб-анимации.
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API


