创建 CSS 动画可能需要学习语法,但要掌握美观直观的动画则需要更多的细微差别。由于动画会吸引很多注意力,因此对代码进行细化以确保时间安排正确并在出现问题时进行调试非常重要。在解决这个问题之后,我想收集一些可以帮助完成此过程的工具。
使用负延迟值
假设您有多个动画同时运行,并且您希望它们稍微错开一点。您可以使用 animation-delay
,但您可能不希望用户访问页面时,有些内容不移动,需要等待延迟。
您可以将 animation-delay
设置为负数,它会将播放头向后移动,以便所有动画在用户出现时运行。当动画共享相同的关键帧值,并且仅通过延迟来区分运动时,这尤其有用。
您可以将此概念用于调试。设置 animation-play-state: paused;
,然后将延迟调整为不同的负时间。您将在补间动画的不同暂停状态下看到动画。
.thing {
animation: move 2s linear infinite alternate;
animation-play-state: paused;
animation-delay: -1s;
}
示例
查看 CodePen 上 CSS-Tricks 的 LVMMGZ 笔记(@css-tricks)。
在一个有趣的演示中,我们看到两个机器人以非常轻微不同的时间悬停,以使感觉更加自然。我们在声明悬停动画时为紫色机器人设置了负延迟,以便在用户首次看到页面时他正在移动。
.teal {
animation: hover 2s ease-in-out infinite both;
}
.purple {
animation: hover 2s -0.5s ease-in-out infinite both;
}
@keyframes hover {
50% {
transform: translateY(-4px);
}
}
查看 CodePen 上 Sarah Drasner 的 Robotter Love 笔记(@sdras)。
多个变换值的弊端
为了获得最佳性能,您还应该使用 transform
来移动和更改内容,并避免使用 margin 或 top/left 等属性造成的重绘成本。Paul Lewis 创建了一个名为 CSS Triggers 的出色资源,它以易于查看的表格形式分解了这些成本。这里的陷阱是,如果您尝试使用多个变换来移动内容,则会出现一些问题。
一个主要问题是顺序。变换不会像预期的那样同时应用,而是在操作顺序中应用。第一个操作是在最右边完成的,然后向内完成。例如,在下面的代码中,缩放将首先应用,然后是平移,最后是旋转。
@keyframes foo {
to {
/* 3rd 2nd 1st */
transform: rotate(90deg) translateX(30px) scale(1.5);
}
}
在大多数情况下,这不是理想的。您更有可能希望所有内容同时发生。此外,当您开始将变换拆分为多个关键帧时,有些值同时应用,有些值不应用,例如
@keyframes foo {
30% {
transform: rotateY(360deg);
}
65% {
transform: translateY(-30px) rotateY(-360deg) scale(1.5);
}
90% {
transform: translateY(10px) scale(0.75);
}
}
这将导致一些令人惊讶且不理想的结果。不幸的是,答案通常是使用多个嵌套的 <div>
,对每个 <div>
应用单一平移,以避免冲突。
查看 CodePen 上 Sarah Drasner 的 Show Order of Operations Transform Fail 笔记(@sdras)。
有一些替代方法,例如使用矩阵变换(手动编码不直观)或使用 JavaScript 动画 API,例如 GreenSock,其中没有针对多个变换插值的顺序序列。
多个 div 实现也可以帮助解决 SVG 中的麻烦错误。在 Safari 中,您无法在动画中同时声明不透明度和变换——其中一个将失败。您可以在本文的第一个演示中看到解决方法的实际操作。
在 2015 年 8 月初,独立变换声明 已迁移到 Chrome Canary。这意味着我们不必再担心顺序问题。您可以分别声明 rotate
、translate
和 scale
。
DevTools 定时助手
Chrome 和 Firefox 现在都提供了一些专门用于帮助处理动画的工具。它们提供一个用于控制速度的滑块、一个暂停按钮以及用于处理缓动值的 UI。将动画速度减慢,并在特定停止点查看动画对于调试 CSS 动画非常有用。
它们都使用 Lea Verou 的 cubic-bezier.com 可视化和 GUI。这非常有用,因为您不再需要在 cubic-bezier.com、文本编辑器和校对之间来回切换。
这些工具使我们能够以更直观的方式微调动画。以下是对这两种工具提供的 UI 的说明


Chrome 和 Firefox 都允许您控制时间安排(加快或减慢),并手动浏览动画。更高级的时间线工具 正在 Chrome 中推出,可以一次查看多个元素。这将非常有用,因为一次只能处理一个元素的动画是一个很大的限制因素。
我遇到过的一个问题是,如果动画持续时间短且速度快,则无法快速抓取元素。在这种情况下,我通常会将其设置为 animation-iteration-count: infinite;
,以便我可以继续操作,而无需与时间作斗争。
我还发现将动画速度减慢,然后使用这些工具在浏览器中重新播放和调整时间安排非常有用。它允许您从根本上拆分每个动作,并查看所有内容的交互方式以及运动进度。如果您以该速度进行细化,那么当您调整它以更快地播放时,它将看起来更加精通。
使用 JavaScript 调试 CSS 动画事件
如果您想确定每个动画的确切触发位置和时间,可以使用一些 JavaScript 来检测并提醒您每个事件何时发生,方法是挂钩到 animationstart
、animationiteration
和 animationend
。
以下是该演示
查看 CodePen 上 Sarah Drasner 的 Showing how to Debug Animation Play States in JavaScript 笔记(@sdras)。
保持关键帧简洁
我经常看到人们在 0% 关键帧和 100% 关键帧中声明相同的属性和值。这是不必要的,会导致代码膨胀。默认情况下,浏览器将采用属性值作为初始值和结束值。
这意味着这有点过分
.element {
animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
0% {
transform: translateX(200px);
}
50% {
transform: translateX(350px);
}
100% {
transform: translateX(200px);
}
}
它可以这样编写
.element {
transform: translateX(200px);
animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
50% {
transform: translateX(350px);
}
}
使动画 DRY
创建美观简洁的动画通常意味着编写非常具体的 cubic-bezier()
缓动函数。经过微调的缓动函数与公司的调色板类似。您的运动具有自己的特定品牌和“声音”。如果您在整个网站上使用它(应该这样,以保持一致性),最简单的方法是将一个或两个缓动函数存储在变量中,就像我们对调色板所做的那样。SASS 和其他预/后处理器使这变得简单
$smooth: cubic-bezier(0.17, 0.67, 0.48, 1.28);
.foo { animation: animation-name 3s $smooth; }
.bar { animation: animation-name 1s $smooth; }
使用 CSS 关键帧进行动画制作时,我们希望尽可能多地从 GPU 获取帮助。这意味着,如果您要对多个对象进行动画制作,您需要一种简单的方法来准备 DOM 以进行即将到来的运动并对元素进行分层。您可以使用标准声明块来硬件加速本机 DOM 元素(不是 SVG)。由于我们在所有要进行动画制作的元素上都重复使用它,因此使用 mixin 或扩展来保存一些输入并添加它是有意义的
@mixin accelerate($name) {
will-change: $name;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
.foo {
@include accelerate(transform);
}
小心。一次卸载太多元素会导致相反的效果,性能下降。大多数动画应该没问题,但如果您使用 haml 之类的工具生成大量 DOM 元素,请注意这一点。
循环以提高性能
Smashing Magazine 最近发布了一篇很棒的文章,展示了很棒的项目Species in Pieces 背后的工作。在特定的一节中,作者详细介绍了如何一次性动画所有片段会导致性能问题。他指出
想象一下,您同时移动 30 个物体;您对浏览器的要求很高,因此这会导致问题是合理的。如果您每个物体都具有 0.199 秒的速度和 0.2 秒的延迟,则可以通过一次只移动一个物体来解决问题。总移动量相同并不重要:如果动画作为链式进行,则性能会立即提高 30 倍。
您可以利用 Sass 或其他预/后处理器 for
循环来实现此类功能。以下是我编写的用于循环遍历 nth-child 的一个非常简单的循环
@for $i from 1 through $n {
&:nth-child(#{$i}) {
animation: loadIn 2s #{$i*0.11}s $easeOutSine forwards;
}
}
不仅如此,您还可以使用它们来交错颜色之类的视觉效果。(点击重新播放以重新运行动画。)
查看代码笔 SASS for loops in CSS Animations,作者 Sarah Drasner (@sdras) 在 CodePen 上发布。
按顺序调整多个动画
当您创建较长的动画时,对多个动画或事件进行排序的方法通常是使用渐进延迟将它们链接在一起。例如
animation: foo 3s ease-in-out, bar 4s 3s ease-in-out, brainz 6s 7s ease-in-out;
但是,假设您正在进行细化,并且发现第一个动画的第二个计数应该更改。这会影响其后所有内容的延迟,因此让我们在之后调整每个内容的时间。没什么大不了的。
animation: foo 2.5s ease-in-out, bar 4s 2.5s ease-in-out, brainz 6s 6.5s ease-in-out;
但是现在让我们添加另一个动画,并再次调整第二个动画的时间(在创建高质量的制作动画时,这种微调会经常发生)。好吧,这开始变得有点低效了。如果您再进行 3 次,那么它就真的非常低效了。
然后想象一下,在动画进行到一半时,有两件事需要同时触发,因此您必须保持两个不同属性的计时一致,并且......好吧,您明白了。这就是为什么每当我进行超过三个或四个链接在一起的动画序列时,我通常会切换到 JavaScript。就我个人而言,我喜欢GreenSock 动画 API,因为它具有非常强大的时间线功能,但是大多数 JS 动画都允许您轻松堆叠动画,而无需任何重新计算,这绝对是一个工作流程优势。
∞
使动画效果良好不仅是构建动画本身,通常,是编辑、细化和调试才能将项目从简单的移动转变为精心编排和高效的片段。希望这些技巧能为您提供更多工具,并能为您工作流程中的部分难点提供帮助。
很棒的文章。其中包含许多非常有用的提示和技巧。
我想知道是否有一些工具可以以类似时间线的方式创作 CSS 动画?类似于 Flash 时代的补间动画,但它会输出 CSS 动画?
不幸的是,目前还没有。有一些类似时间线动画的工具,例如 Edge Animate 和 Animatron,可以导出各种类型的 JavaScript 动画。还有一些方便的工具,例如 bounce.js (http://bouncejs.com/),可以帮助您以可视化方式创建矩阵转换动画。
对于 CSS 来说没有,但是完全像 Flash 时代一样(因为您以前可能在 Flash 中使用过它):GreenSock 具有一个 JS 动画 API,它具有非常强大的时间线功能:http://greensock.com/timelinemax。否则,bodymovin.js 允许您在 After Effects 中创建内容并导出到 JavaScript。 https://github.com/bodymovin/bodymovin。希望这能有所帮助!
这是一个编写导出 CSS 工具的绝佳机会!(或者也许只是现有工具的替代导出器。)
也许像这样? http://cssanimate.com/
看看吧!太酷了。很棒的提示。
感谢这篇文章,非常清晰易懂!
谢谢!很高兴您觉得它有用!
您文章中的信息很棒。从您这里学到了新的技巧。
使用 CSS,我们可以轻松地生成图形和动画。开发工具和循环非常适合此。干得好。感谢分享。
http://www.discoverwebdesignmelbourne.com.au/
不错的文章!
相对于获得 GPU 的帮助,同时使用
will-change
、backface-visibility
和transform
属性是不是有点过分了?问得好!我通常不太使用 will-change,只使用其他三个,但我认识其他人发誓要使用它。值得快速测试一下,看看添加 will-change 是否有优势(我已经运行过测试,结果表明其他三个有很大的优势 - 请参见 https://css-tricks.org.cn/weighing-svg-animation-techniques-benchmarks/),但应该再次同时使用和不使用 will-change 在 div 上进行测试。我个人建议测试任何与性能相关的功能。
很棒的文章!
关于多个转换的问题,可以通过在具有较少转换值的关键帧中添加“none”来轻松解决。
演示
http://codepen.io/brianknapp/pen/QbYboK
所以!当我看到您的示例时,我真的很兴奋,但是经过一些非常仔细的比较和(这里有一个笑话)调试之后,您的技术实际上并不相同 - 存在细微但可辨别的差异,而且有点不准确 - 可能是只有像我这样的人才会注意到的差异 - 但是如果您试图完善一些微小的细节,它确实会产生影响。我的意思是
查看代码笔 Show Order of Operations Transform Fail,作者 Sarah Drasner (@sdras) 在 CodePen 上发布。
但是如果我对这一点有误,请告诉我,我一直在努力学习。
你说得对。经过大量研究之后,我不确定 none 除了平滑一些状态之外,实际上还能做什么。它还会截断最后一个关键帧,这不好 :(
实际上,转换始终是“同时”动画的,因为没有不同的转换,而只有一个转换(就像 CSS 语法一样,所有转换函数都构成单个
transform
属性的单个值),它具有多个组件,通过多个中间值进行动画。问题是,转换组件的缺失值表示其默认值,而不是“不变”。即等效于
但是,如果我们将这些默认值替换为手动插值的默认值,我们可以实现几乎与不同转换的包装器的单独动画相同的结果:http://codepen.io/SelenIT/pen/pJYOmY
因此,我认为,说“转换一个接一个地应用”是一种过于简化的说法。
您好 SelenIT!您的代码笔很棒,我认为这是我见过的第一个演示如何为所有转换值使用一个 div 的代码笔,我将在文章中编辑以包含此解决方案。太棒了。
我要说的一件事是,编写这个并不直观 - 考虑到您需要将比例从 1.5 调整为 1.75,在 60% 而不是 65% 的位置调整。然后您必须重新计算所有其他通常会为您插值的数值。我认为这种工作流程堵塞可能会在动画变得更加复杂或您试图同时细化一些内容时真正拖慢您的速度。尽管如此,您避免了使用多个 div,因此这实际上取决于您要优化的内容。再次感谢您的意见,我相信这对大家都会有所帮助。