Управление сложностью с помощью API веб-анимации
Опубликовано: 2022-03-10Между простыми переходами и сложной анимацией нет золотой середины. Либо вас устраивает то, что предоставляют CSS-переходы и анимация, либо вам внезапно понадобилась вся мощь, которую вы можете получить. Web Animations API предоставляет множество инструментов для работы с анимацией. Но нужно знать, как с ними обращаться. Эта статья познакомит вас с основными моментами и методами , которые могут помочь вам справиться со сложной анимацией, оставаясь при этом гибким.
Прежде чем мы углубимся в статью, важно, чтобы вы были знакомы с основами API веб-анимации и JavaScript. Чтобы сделать его понятным и не отвлекать от проблемы, приведенные примеры кода просты. Ничего сложнее функций и объектов не будет. В качестве хороших точек входа в сами анимации я бы предложил MDN в качестве общего справочника, отличную серию Daniel C. Wilson и CSS Animations vs Web Animations API Олли Уильямса. Мы не будем рассматривать способы определения эффектов и их настройки для достижения желаемого результата. В этой статье предполагается, что у вас есть определенные анимации и вам нужны идеи и методы для их обработки.
Начнем с обзора интерфейсов и того, для чего они нужны. Затем мы рассмотрим сроки и уровни контроля, чтобы определить, что, когда и как долго. После этого мы научимся обрабатывать несколько анимаций как одну, оборачивая их в объекты. Это было бы хорошим началом на пути к использованию API веб-анимации.
Интерфейсы
Web Animations API дает нам новое измерение контроля. До этого CSS-переходы и анимация, предоставляя мощный способ определения эффектов, по-прежнему имели единую точку срабатывания . Как выключатель света, он либо был включен, либо выключен. Вы можете играть с задержками и функциями плавности для создания довольно сложных эффектов. Тем не менее, в определенный момент он становится громоздким и трудным для работы.
Web Animations API превращает эту единственную точку срабатывания в полный контроль над воспроизведением . Выключатель света превращается в диммер с ползунком. Если вы хотите, вы можете превратить его в умный дом, потому что в дополнение к управлению воспроизведением вы теперь можете определять и изменять эффекты во время выполнения. Теперь вы можете адаптировать эффекты к контексту или реализовать редактор анимации с предварительным просмотром в реальном времени.
Начнем с интерфейса анимации. Чтобы получить объект анимации, мы можем использовать метод Element.animate
. Вы даете ему ключевые кадры и параметры, и он сразу же воспроизводит вашу анимацию. Что он также делает, так это возвращает экземпляр объекта Animation
. Его цель - управлять воспроизведением.
Думайте об этом как о кассетном плеере , если вы помните такие. Я понимаю, что некоторые читатели могут не знать, что это такое. Неизбежно, что любая попытка применить концепции реального мира для описания абстрактных компьютерных вещей быстро потерпит неудачу. Но пусть успокоит вас — читателя, не знающего радости перемотки кассеты карандашом, — что люди, знающие, что такое кассетный плеер, к концу этой статьи запутаются еще больше.
Представьте коробку. У него есть слот для кассеты и кнопки для воспроизведения, остановки и перемотки. Это то, чем является экземпляр интерфейса Animation — поле, которое содержит определенную анимацию и предоставляет способы взаимодействия с ее воспроизведением. Вы даете ему что-то, чтобы играть, и он возвращает вам контроль.
Элементы управления, которые вы получаете, очень похожи на те, которые вы получаете от аудио- и видеоэлементов. Это методы воспроизведения и паузы , а также свойство текущего времени . С помощью этих трех элементов управления вы можете создать что угодно, когда дело доходит до воспроизведения.
Сама кассета представляет собой пакет, который содержит ссылку на анимируемый элемент, определение эффектов и параметры, включающие, среди прочего, синхронизацию. И это то, что KeyframeEffect
. Наша кассета содержит все записи и информацию о продолжительности записей. Я оставлю воображению старшей аудитории сопоставить все эти свойства с компонентами физической кассеты. Я покажу вам, как это выглядит в коде.
Когда вы создаете анимацию с помощью Element.animate
, вы используете ярлык, который делает три вещи. Он создает экземпляр KeyframeEffect
. Он помещается в новый экземпляр Animation
. Он сразу начинает играть.
const animation = element.animate(keyframes, options);
Давайте разберем его и посмотрим на эквивалентный код, который делает то же самое.
const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)
Возьмите кассету (1), вставьте ее в проигрыватель (2), затем нажмите кнопку воспроизведения (3).
Суть знания того, как это работает за кулисами, состоит в том, чтобы иметь возможность разделить определение ключевых кадров и решить, когда их воспроизводить. Когда у вас есть много анимаций для координации, может быть полезно сначала собрать их все, чтобы вы знали, что они готовы к воспроизведению. Генерировать их на лету и надеяться, что они начнут играть в нужный момент, — это не то, на что хотелось бы надеяться. Слишком легко испортить желаемый эффект перетаскиванием нескольких кадров. В случае длинной последовательности это сопротивление накапливается, что приводит к не совсем убедительному восприятию.
Сроки
Как и в комедии, в анимации все решает время. Чтобы заставить эффект работать, чтобы добиться определенного ощущения, вам нужно иметь возможность точно настроить способ изменения свойств. Есть два уровня синхронизации , которыми вы можете управлять в Web Animations API.
На уровне отдельных свойств у нас есть offset
. Смещение дает вам контроль над синхронизацией одного свойства . Присвоив ему значение от нуля до единицы, вы определяете, когда срабатывает каждый эффект. Если его опустить, оно равно нулю.
Возможно, вы помните из @keyframes
в CSS, как вы можете использовать проценты вместо from
/ to
. Вот что такое offset
, но деленное на сто. Значение offset
является частью продолжительности одной итерации .
offset
позволяет расположить ключевые кадры внутри KeyframeEffect
. Относительное числовое смещение гарантирует, что независимо от продолжительности или скорости воспроизведения все ваши ключевые кадры начнутся в один и тот же момент относительно друг друга.
Как мы уже говорили ранее, offset
— это часть продолжительности . Теперь я хочу, чтобы вы избегали моих ошибок и потери времени на это. Важно понимать, что продолжительность анимации — это не то же самое, что общая продолжительность анимации. Обычно они одинаковые, и это то, что могло вас смутить, и то, что определенно смутило меня.
Длительность — это количество времени в миллисекундах , которое требуется для завершения одной итерации. По умолчанию она будет равна общей продолжительности. Как только вы добавите задержку или увеличите количество итераций в продолжительности анимации, она перестанет сообщать вам число, которое вы хотите знать. Это важно понять, чтобы использовать это в своих интересах.
Когда вам нужно скоординировать воспроизведение ключевого кадра в более широком контексте, таком как воспроизведение мультимедиа, вам нужно использовать параметры синхронизации. Вся продолжительность анимации от начала до «завершенного» события в следующем уравнении:
delay + (iterations × duration) + end delay
Вы можете увидеть это в действии в следующей демонстрации:
См. Pen [Какова фактическая продолжительность анимации?] (https://codepen.io/smashingmag/pen/VwWWrzz) Кирилла Мышкина.
Это позволяет нам выровнять несколько анимаций в контексте мультимедиа фиксированной длины. Сохраняя желаемую продолжительность анимации без изменений, вы можете «дополнить» ее delay
в начале и delayEnd
в конце, чтобы встроить ее в контекст с большей продолжительностью. Если подумать, delay
в этом смысле будет действовать так же, как смещение в ключевых кадрах. Просто помните, что задержка устанавливается в миллисекундах, поэтому вы можете преобразовать ее в относительное значение.

Еще один параметр синхронизации, который поможет выровнять анимацию, — это iterationStart
. Он устанавливает начальную позицию итерации. Возьмите демонстрацию бильярдного шара. Регулируя ползунок iterationStart
, вы можете установить начальную позицию мяча и вращение, например, вы можете настроить его так, чтобы он начинал прыгать из центра экрана и чтобы число было прямо в камере в последнем кадре.
См. Pen [Tweak interationStart] (https://codepen.io/smashingmag/pen/qBjjVPR) Кирилла Мышкина.
Управляйте несколькими как одним
Когда я работал над редактором анимации для приложения для презентаций, мне приходилось размещать несколько анимаций для одного элемента на временной шкале. Моей первой попыткой было использовать offset
, чтобы поместить анимацию в правильную начальную точку на временной шкале.
Это быстро оказалось неправильным способом использования offset
. С точки зрения этого конкретного пользовательского интерфейса, движущаяся анимация на временной шкале означает смещение ее начальной позиции без изменения продолжительности анимации. Со offset
это означало, что мне нужно было изменить несколько вещей, само offset
, а также изменить offset
свойства закрытия, чтобы убедиться, что продолжительность не изменится. Решение оказалось слишком сложным для понимания.
Вторая проблема возникла со свойством transform
. Из-за того, что он может представлять несколько характерных изменений элемента, может быть сложно заставить его делать то, что вы хотите. В случае желания изменить эти свойства независимо друг от друга, это может стать еще сложнее. Функция изменения масштаба влияет на все следующие за ней функции. Вот почему это происходит.
Свойство Transform может принимать в качестве значения несколько функций в последовательности . В зависимости от порядка функции результат меняется. Возьми scale
и translate
. Иногда удобно определить translate
в процентах, что означает относительно размера элемента. Скажем, вы хотите, чтобы мяч подпрыгнул ровно на три собственных диаметра. Теперь в зависимости от того, где вы поместите функцию масштабирования — до или после translate
— результат будет меняться от трех высот исходного размера или масштабированного.
Это важная черта свойства transform
. Это нужно для достижения довольно сложной трансформации. Но когда вам нужно, чтобы эти преобразования были отдельными и независимыми от других преобразований элемента, это мешает.
Бывают случаи, когда вы не можете поместить все эффекты в одно свойство transform
. Это может стать слишком много довольно быстро. Особенно, если ваши ключевые кадры берутся из разных мест, вам потребуется очень сложное слияние преобразованной строки . Вы вряд ли можете полагаться на автоматический механизм, потому что логика не проста. Кроме того, может быть трудно понять, чего ожидать. Чтобы упростить это и сохранить гибкость, нам нужно разделить их на разные каналы.
Одним из решений является обернуть наши элементы в div
, каждый из которых можно анимировать отдельно, например, div для позиционирования на холсте, другой для масштабирования и третий для поворота. Таким образом, вы не только значительно упрощаете определение анимации, но и открываете возможность определения различных источников преобразования, где это применимо.
Может показаться, что с этим трюком все выходит из-под контроля. Что мы умножаем количество проблем, которые у нас были раньше. На самом деле, когда я впервые нашел этот трюк, я отбросил его как слишком много. Я подумал, что могу просто убедиться, что мое свойство transform
скомпилировано из всех частей в правильном порядке в одну часть. Потребовалась еще одна функция transform
, чтобы сделать вещи слишком сложными для управления и сделать некоторые вещи невозможными. Моему компилятору строки свойства transform
требовалось все больше и больше времени, чтобы все исправить, поэтому я сдался.
Оказалось, что управлять воспроизведением нескольких анимаций не так уж и сложно, как кажется изначально. Помните аналогию с кассетным магнитофоном с самого начала? Что, если бы вы могли использовать свой собственный проигрыватель, который принимает любое количество кассет? Более того, вы можете добавить на этот плеер столько кнопок, сколько захотите.
Единственная разница между вызовом play
для одной анимации и массивом анимаций заключается в том, что вам нужно выполнить итерацию. Вот код, который вы можете использовать для любого метода экземпляров Animation
:
// To play just call play on all of them animations.forEach((animation) => animation.play());
Мы будем использовать это для создания всевозможных функций для нашего плеера.
Давайте создадим этот ящик, в котором будут храниться анимации и воспроизводить их. Вы можете создавать эти коробки любым подходящим способом. Чтобы было понятно, я покажу вам пример того, как это сделать с помощью функции и объекта. Функция createPlayer
принимает массив анимаций, которые должны воспроизводиться синхронно. Он возвращает объект с одним методом play
.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }
Вам этого достаточно знать, чтобы приступить к расширению функционала. Добавим методы pause и currentTime
.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }
createPlayer
с этими тремя методами дает вам достаточно контроля для организации любого количества анимаций . Но давайте подтолкнем его немного дальше. Сделаем так, чтобы наш плеер мог брать не только любое количество кассет, но и других плееров.
Как мы видели ранее, интерфейс Animation
похож на медиа-интерфейсы. Используя это сходство, вы можете добавить в свой плеер все что угодно. Чтобы приспособиться к этому, давайте настроим метод currentTime
, чтобы он работал как с объектами анимации, так и с объектами, полученными из createPlayer
.
function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }
Только что созданный нами проигрыватель позволит вам скрыть сложность нескольких div
для каналов одноэлементной анимации. Эти элементы могут быть сгруппированы в сцену. И каждая сцена может быть частью чего-то большего. Все, что можно было сделать с помощью этой техники.
Чтобы продемонстрировать временную демонстрацию, я разделил все анимации на трех игроков. Первый — управлять воспроизведением превью справа. Второй сочетает в себе анимацию прыжков всех контуров шаров слева и одного в превью.
Наконец, третий — игрок, совместивший анимацию положения шаров в левом контейнере. Этот игрок позволяет шарам растекаться в непрерывной демонстрации анимации с фрагментами около 60 кадров в секунду.
Заключение
Веб-интерфейсы, такие как API веб-анимации, раскрывают нам некоторые вещи, которые браузеры всегда делали. Браузеры знают, как быстро выполнять рендеринг, передавая работу графическому процессору. С Web Animations API мы можем контролировать это. Хотя этот элемент управления может показаться немного странным или запутанным, это не значит, что его использование также должно сбивать с толку. Имея представление о времени и управлении воспроизведением, у вас есть инструменты, чтобы приручить этот API в соответствии с вашими потребностями. Вы должны быть в состоянии определить, насколько сложной она должна быть.
Дальнейшее чтение
- «Практические методы создания анимации», Сара Драснер
- «Проектирование с уменьшенным движением для чувствительности к движению», Вэл Хед.
- «Альтернативный голосовой интерфейс для голосовых помощников», Оттоматиас Пеура
- «Разработка лучших всплывающих подсказок для мобильных пользовательских интерфейсов», Эрик Олив