使用 Web 动画 API 编排复杂性

已发表: 2022-03-10
快速总结↬ Web Animations API 中的选项太多了,无法轻松地选择它们。 了解计时的工作原理以及如何同时控制多个动画的播放为您的项目奠定坚实的基础。

简单的过渡和复杂的动画之间没有中间地带。 你要么对 CSS 过渡和动画提供的东西很好,要么你突然需要你能得到的所有力量。 Web Animations API 为您提供了许多处理动画的工具。 但是你需要知道如何处理它们。 本文将引导您了解可以帮助您处理复杂动画同时保持灵活性的要点和技术

在我们深入研究本文之前,您必须熟悉 Web Animations API 和 JavaScript 的基础知识。 为了清楚起见并避免分心手头的问题,提供的代码示例很简单。 没有比函数和对象更复杂的了。 作为动画本身的不错切入点,我建议 MDN 作为一般参考,Daniel C. Wilson 的优秀系列,以及 Ollie Williams 的 CSS Animations vs Web Animations API。 我们不会通过定义效果和调整它们以达到您想要的结果的方法。 本文假设您已定义动画并需要处理它们的想法和技术。

我们首先概述接口及其用途。 然后,我们将查看控制时间和级别,以定义什么、何时以及持续多长时间。 之后,我们将学习如何通过将多个动画包装在对象中来将它们视为一个。 这将是您使用 Web Animations 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 。 Offset 让您可以控制单个属性的时间。 通过给它一个从零到一的值,您可以定义每个效果何时生效。如果省略,它等于零。

您可能还记得 CSS 中的@keyframes如何使用百分比而不是from / to 。 这就是offset除以一百。 offset的值是单次迭代持续时间的一部分

offset允许您在KeyframeEffect中排列关键帧。 作为一个相对数字偏移量,可以确保无论播放的持续时间或速率如何,所有关键帧都在相对于彼此的同一时刻开始。

如前所述, offsetduration 的一部分。 现在我希望你避免我的错误和在这方面浪费时间。 重要的是要了解动画的持续时间与动画的整体持续时间不同。 通常,它们是相同的,这可能会让您感到困惑,而这肯定会让我感到困惑。

持续时间是完成一次迭代所需的时间量(以毫秒为单位)。 默认情况下,它将等于总持续时间。 一旦您添加延迟或增加动画持续时间中的迭代次数,就会停止告诉您您想知道的数字。 理解这一点很重要,以便将其用于您的优势。

当您需要在更大的上下文中协调关键帧播放时,例如媒体播放,您需要使用计时选项。 动画从开始到“完成”事件的整个持续时间如下等式:

 delay + (iterations × duration) + end delay

您可以在以下演示中看到它的实际效果:

请参阅 Kirill Myshkin 的 Pen [动画的实际持续时间是多少?](https://codepen.io/smashingmag/pen/VwWWrzz)。

查看 Pen 动画的实际持续时间是多少? 基里尔·米什金(Kirill Myshkin)。

这允许我们在固定长度媒体的上下文中对齐多个动画。 保持所需的动画持续时间不变,您可以在开始时用delay “填充”它,在结束时用delayEnd来“填充”它,以便将其嵌入到持续时间更长的上下文中。 如果您考虑一下,从这个意义上讲, delay就像关键帧中的偏移一样。 请记住延迟以毫秒为单位设置,因此您可能希望将其转换为相对值。

另一个有助于对齐动画的计时选项是iterationStart 。 它设置迭代的起始位置。 以台球演示为例。 通过调整iterationStart滑块,您可以设置小球的起始位置和旋转,例如,您可以设置它从屏幕中心开始跳跃,并使数字在最后一帧的相机中保持直线。

请参阅 Kirill Myshkin 的 Pen [Tweak interationStart](https://codepen.io/smashingmag/pen/qBjjVPR)。

请参阅 Kirill Myshkin 的 Pen Tweak interationStart。

多管齐下

当我为演示应用程序制作动画编辑器时,我必须为时间轴上的单个元素安排多个动画。 我的第一次尝试是使用offset将我的动画放在时间轴上的正确起点。

这很快被证明是使用offset的错误方式。 就时间轴上这个特定的 UI 移动动画而言,意味着在不改变动画持续时间的情况下改变其起始位置。 使用offset意味着我需要更改一些东西,即offset本身并更改关闭属性的offset以确保持续时间不会改变。 该解决方案被证明太复杂而无法理解。

第二个问题来自transform属性。 由于它可以代表一个元素的几个特征变化,让它做你想做的事情会变得很棘手。 如果希望彼此独立地更改这些属性,则可能会变得更加困难。 尺度函数的变化会影响它之后的所有函数。 这就是发生这种情况的原因。

Transform 属性可以将序列中的多个函数作为值。 根据函数的顺序,结果会发生变化。 采取scaletranslate 。 有时用百分比定义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 帧的切片速度在动画的连续演示中传播。

结论

像 Web Animations API 这样的 Web 界面向我们展示了浏览器一直以来所做的某些事情。 浏览器知道如何通过将工作传递给 GPU 来快速渲染。 使用 Web Animations API,我们可以控制它。 尽管该控件可能看起来有点陌生或令人困惑,但这并不意味着使用它也应该令人困惑。 了解时间和回放控制后,您就有了工具来根据您的需要来调整 API。 您应该能够定义它应该有多复杂。

延伸阅读

  • “设计动画的实用技巧,”莎拉·德拉斯纳
  • “针对运动灵敏度进行减少运动设计”,Val Head
  • “语音助手的替代语音 UI”,Ottomatias Peura
  • “为移动用户界面设计更好的工具提示”,Eric Olive