CSS 动画
CSS 中的 transform
,transition
和 animation
是分开的三部分内容,其中 transfrom
主要是控制元素变形,并没有一个时间控制的概念,而 transition
和 animation
才是动画的部分,它们可以控制在一个时间段里,元素在两个或以上的状态切换的效果。
基本上我们会有这样的一个简单的概念,CSS 的动画效果由浏览器控制和渲染,理论上比 JavaScript 的动画效果性能好,但是控制上没有 JavaScript 那么灵活方便。
迪士尼出版的一本书中提及了动画效果的十二个原则,这篇文章讲解得比较详细,并且将其结合到页面动画中:网页动画的十二原则。
transition
transition
允许我们在 CSS 属性变化时给它添加一个过度的动画效果。通常情况下,CSS 属性变化是立即生效的,新的属性值在超级短的时间内替换掉旧的属性值,然后浏览器重新绘制样式内容(可能是 reflow 或者 repaint)。大部分情况下会感觉样式变化突兀,而 transition
则可以添加顺滑的一个变化效果。例如:
.content {
background: magenta;
transition: background 200ms ease-in 50ms;
}
.content:hover {
background: yellow;
transition: background 200ms ease-out 50ms;
}
transition
的兼容性,不算差,基本上移动设备都可以使用了,并且能做到渐进增强,支持的便有过渡效果,不支持的便是直接切换,所以可以放心使用。
transition 属性
CSS 的 transition
有四个属性:
-
transition-delay
延迟多久后开始动画 -
transition-duration
过渡动画的一个持续时间 -
transition-property
执行动画对应的属性,例如color
,background
等,可以使用all
来指定所有的属性 -
transition-timing-function
随着时间推进,动画变化轨迹的计算方式,常见的有:linear,ease,ease-in,ease-out,cubic-bezier(...) 等。详细参考:transition-timing-function,里边有各个效果的简单例子。
这四个属性可以简写成为:
.class {
transition: <property> <duration> <timing-function> <delay>
}
例如前边的那个例子,当 .content
元素 hover 时,50 毫秒后背景颜色从 magenta 渐变到 yellow,持续时间 200 毫秒,使用的是 ease-out 的算法。留意下:transition
生效的是对应的选择器的属性,例如 .content:hover
中的 transition
便是从 .content
的 magenta 到 yellow 过渡效果的控制,而 .content
中的 transition
则是控制不 hover 时,背景颜色从 yellow 到 magenta 的变化过程。
all
这个属性值是这样的,它对应选择器下的元素的所有 CSS 属性生效,无论在哪里声明的 CSS 规则,并不局限于在同个代码块下。
如果需要不同属性对应不同的效果,可以这么来写:
.demo {
transition-property: all, border-radius, opacity;
transition-duration: 1s, 2s, 3s;
/* 当这样使用时,确保 all 在第一个,因为如果 all 在后边的话,它的规则会覆盖掉前边的属性 */
}
transition
的 none
属性较少用到,一般用于移除原本有的动画效果。none
没法和逗号一起使用来移除特定属性的动画效果,只能直接干掉 transition
,如果要移除特定的属性效果,可以重写 transition
而不把要移除的属性写进去,或者比较 trick 的做法是设置 duration
为 0。
并不是所有的 CSS 属性都是可以添加 transition
效果的。详细可以参考文档:animatable properties。可能经常遇到的就是 display
这个属性并不能添加 transition
效果,你可以考虑使用 visibility
或者后边会提及的 animation
。
关于 transition-timing-function
的各个算法的一个变化曲线是怎么样的,我们可以使用 chrome 的开发者工具来看一下,CSS 中你编写了对应的 transition
后,把鼠标移到 transition-timing-function
的那个值前边,如下图:
这样你便可以很清晰地看到这个算法的一个变化轨迹是怎么用的,然后选择符合自己需要的一个算法。
transition 相关的事件
transitionend
事件会在 transition
动画结束的时候触发。通常我们会在动画结束后执行一些方法,例如继续下一个动画效果或者其他。Zepto.js 中的动画方法都是使用 CSS 动画属性来处理,而其中动画运行后的回调便应该是使用这个事件来处理。
transitionend
事件触发时会传入一些动画相关的参数,例如:propertyName
,elapsedTime
,详细内容可以参考:transitionend。
transition 应用
transition
在很多 UI 框架中是很常见的属性,当我们开发一个交互效果的时候,从某个状态到达另外一个状态时,transition
可以使得这个过程变得更加舒适和顺滑。例如上边的 hover
时的背景颜色的切换,控制元素的显示和隐藏时使用 opacity
来实现渐隐渐现。
当 transition
配合上 transform
提供的多样化的元素变化能力后,便可以绘制出很多有趣的交互渐变效果了。�最近使用过程中做的一个简单效果的例子,点击查看。
很常见还有表单 input 报错时边框变红,按钮 hover 时背景渐变等,很多的 CSS 交互效果会因为 transition
变得更加自然。
animation
虽然 transition
已经提供了很棒的动画效果了,但是我们只能够控制从一个状态到达另外一个状态,没法来控制多个状态的不断变化,而 animation
而帮助我们实现了这一点。使用 animation
的前提是我们需要先使用 @keyframes
来定义一个动画效果,@keyframes
定义的规则可以用来控制动画过程中的各个状态的情况,语法大抵是这个样子:
@keyframes W {
from { left: 0; top: 0; }
to { left: 100%; top: 100%; }
}
@keyframes
关键词后跟动画的名字,然后是一个块,块中有动画进度的各个选择器,选择器后的块则依旧是我们常见的各个 CSS 样式属性。
在这里,控制动画的整个过程的选择器很重要,语法相对简单,你可以使用 from
或者 0%
来表示起始状态,而 to
或 100%
来表示结束状态。中间的部分你都可以使用百分比来进行表示。选择器后的块则是在到达这个进度状态时元素的样式应该是怎么样的,整个的过渡动画在这个的控制基础上由浏览器去绘制。
同样地,不是所有的属性都可以有动画效果,MDN 维护了一份 CSS 动画的属性列表 可供参考。
通常来说,多个状态下的相同属性的值应该是可以取到它们的中间值的,例如 left 从 0% 到 100%,如果没法取到中间值,如 height 从 auto 到 100px,有可能出现奇怪的一些状况,并且不同浏览器对此的处理也不尽相同,所以请尽量避免这种情况。
animation 属性
animation
的属性比 transition
多,如下:
-
animation-name
你需要的动画效果的@keyframes
的名字。 -
animation-delay
和transition-delay
一样,动画延迟的时间。 -
animtaion-duration
和transition-duration
一样,动画持续的时间。 -
animation-direction
动画的一个方向控制。
默认是normal
,如果是上述的 left 从 0% 到 100%,那么默认是从左到右。如果这个值是reverse
,那么便是从右到左。由于
animation
提供了循环的控制,所以还有两个值是alternate
和alternate-reverse
,这两个值会在每次循环开始的时候调转动画方向,只不过是起始的方向不同。例如还是 left 的例子,假设设置了
animation-direction: alternate; animation-iteration-count: infinite;
,那么这个元素从左到右移动后,便调转方向,从右到左,如此循环。 -
animation-fill-mode
这个属性用来控制动画前后,@keyframes
中提供的 CSS 属性如何应用到元素上。
默认值是none
,还有其他三个选择:forwards
,backwards
,both
。假设是
none
,那么动画前后,动画中声明的 CSS 属性都不会应用到元素上。即动画效果执行后,元素便恢复正常状态。如果是
forwards
,那么动画结束后,会把最后状态的 CSS 属性应用到元素上,即保持动画最后的样子。而backwards
则相反,both
则都会,计算得出最后的一个结果。 -
animation-timing-function
和transition-timing-function
一样,动画变化轨迹的算法。 -
animation-iteration-count
动画循环次数,如果是infinite
则无限次。有趣的是,支持小数,即 0.5 表示动画执行到一半。 -
animation-play-state
动画执行的状态,两个值running
或者paused
,可以用来控制动画是否执行。
上述这些属性可以简写为:
.class {
animation: <duration> <timing-function> <delay> <iteration-count> <direction> <fill-mode> <play-state> <name>
}
略长,当然,平时使用中可能是省略部分参数的。
animation 需要留意的东西
优先级
记得 CSS 中的层叠概念么,优先级高的属性会覆盖优先级低的属性,当 animation 应用到元素中时,动画运行过程中,@keyframes
声明的 CSS 属性优先级最高,比行内声明 !important
的样式还要高。现在浏览器的实现是这样子的,但是标准文档中的说法应该是可以被 !important
声明的属性所覆盖。
多个动画的顺序
由于 animation-name
是可以指定多个动画效果的,所以这里便会出现动画的一个顺序问题。后指定的动画会覆盖掉前边的,例如:
#colors {
animation-name: red, green, blue; /* 假设这些 keyframe 都是修改 color 这个属性 */
animation-duration: 5s, 4s, 3s;
}
上述代码的动画效果会是这样:前 3 秒是 blue,然后接着 1 秒是 green,最后 1 秒是 red。整个覆盖的规则是比较简单的。
display 的影响
如果一个元素的 display
设置为 none
,那么在它或者它的子元素上的动画效果便会停止,而重新设置 display
为可见后,动画效果会重新重头开始执行。
animation 相关事件
我们可以通过绑定事件来监听 animation 的几个状态,这些事件分别是:
- animationstart 动画开始事件,如果有 delay 属性的话,那么等到动画真正开始再触发,如果是没有 delay,那么当动画效果应用到元素时,这个事件会被触发。
- animationend 动画结束的事件,和 transitionend 类似。如果有多个动画,那么这个事件会触发多次,像上边的例子,这个事件会触发三次。如果
animation-iteration-count
设置为infinite
,那么这个事件则不会被触发。 - animationiteration 动画循环一个生命周期结束的事件,和上一个事件不一样的是,这个在每次循环结束一段动画时会触发,而不是整个动画结束时触发。无限循环时,除非 duration 为 0,否则这个事件会无限触发。
animation event 相关的属性可以参考:animationEvent。
animation 应用
animation
可以实现控制在多个状态下进行动画切换,所以应用的场景比 transition
要广泛得多,可以使用 animation 实现大量的动效,具体可以查看下 animate.css 这个库。
我上边提到的 简单例子 中也包括了一个简单地使用 animation
来缩放字体,和一个简单进度条的例子。
CSS 动画的性能
现在越来越多的页面开发是面向移动端,所以我们会更加关注性能方面的问题。浏览器绘制动画的过程中,涉及的主要是 Layout,Paint,Composite 的处理,当一个动画触发的浏览器处理越少,影响的区域越少,那么便消耗越低,性能上越好。
我们可以参考这个 CSS �Triggers 网站提供的列表,这里展示了修改属性时对应的浏览器内核所需要做的处理。简单概括来说,动画尽量少涉及布局相关的调整,因为布局上一旦变化,会涉及外部元素和内部元素的位置调整,对浏览器的消耗相当大,而在现代浏览器中,拥有比较好动画性能的属性就是 transform
和 opacity
,建议需要动画的时候多往这两个属性考虑,例如字体要放大,避免使用 font-size
,而是用 transform: scale()
等。
我们可以使用 will-change
来声明即将变化的属性,这可以让浏览器提前做一些优化工作,关于这个属性,更多内容可以参考: will-change。值得留意的是,别滥用 will-change
,在太多的元素上使用或者应用太多的属性都会导致浏览器资源浪费。
Chrome 浏览器的开发者工具提供的 Timeline 工具可以帮助我们来查看页面渲染的一个性能表现情况,如下图:
Chrome TimelineTimeline 可以用来获取脚本执行性能,网络请求性能等的表现数据,但是这里我们只是关于动画渲染,所以只是勾选了 paint。我们可以看到渲染可以保持在 60 FPS,便不会感觉到卡顿。当渲染 FPS 过低的时候,图示那里会出现红色的提示,通过这个工具可以帮助我们在需要的时候做针对性的优化。
对于 CSS 动画性能上的东西,本人还比较缺乏实践经验,最近 w3cplus 上出现了大量的关于 CSS 动画性能相关的文章,有兴趣可以查阅。