共计 15967 个字符,预计需要花费 40 分钟才能阅读完成。
本文将比拟全面粗疏的梳理一下 CSS 动画的方方面面,针对每个属性用法的解说及进阶用法的示意,心愿能成为一个比拟好的从入门到进阶的教程。
CSS 动画介绍及语法
首先,咱们来简略介绍一下 CSS 动画。
最新版本的 CSS 动画由标准 — CSS Animations Level 1 定义。
CSS 动画用于实现元素从一个 CSS 款式配置转换到另一个 CSS 款式配置。
动画包含两个局部: 形容动画的款式规定和用于指定动画开始、完结以及两头点款式的关键帧。
简略来说,看上面的例子:
div {animation: change 3s;}
@keyframes change {
0% {color: #f00;}
100% {color: #000;}
}
animation: move 1s
局部就是动画的第一局部,用于形容动画的各个规定;@keyframes move {}
局部就是动画的第二局部,用于指定动画开始、完结以及两头点款式的关键帧;
一个 CSS 动画肯定要由上述两局部组成。
CSS 动画的语法
接下来,咱们简略看看 CSS 动画的语法。
创立动画序列,须要应用 animation 属性或其子属性,该属性容许配置动画工夫、时长以及其余动画细节,但该属性不能配置动画的理论体现,动画的理论体现是由 @keyframes 规定实现。
animation 的子属性有:
- animation-name:指定由 @keyframes 形容的关键帧名称。
- animation-duration:设置动画一个周期的时长。
- animation-delay:设置延时,即从元素加载实现之后到动画序列开始执行的这段时间。
- animation-direction:设置动画在每次运行完后是反向运行还是从新回到开始地位反复运行。
- animation-iteration-count:设置动画反复次数,能够指定 infinite 有限次反复动画
- animation-play-state:容许暂停和复原动画。
- animation-timing-function:设置动画速度,即通过建设加速度曲线,设置动画在关键帧之间是如何变动。
- animation-fill-mode:指定动画执行前后如何为指标元素利用款式
- @keyframes 规定,当然,一个动画想要运行,还应该包含 @keyframes 规定,在外部设定动画关键帧
其中,对于一个动画:
- 必须项 :
animation-name
、animation-duration
和@keyframes
规定 - 非必须项:
animation-delay
、animation-direction
、animation-iteration-count
、animation-play-state
、animation-timing-function
、animation-fill-mode
,当然不是说它们不重要,只是不设置时,它们都有默认值
下面曾经给了一个简略的 DEMO,就用上述的 DEMO,看看后果:
这就是一个最根本的 CSS 动画,本文将从 animation 的各个子属性动手,探索 CSS 动画的方方面面。
animation-name / animation-duration 详解
整体而言,单个的 animation-name
和 animation-duration
没有太多的技巧,十分好了解,放在一起。
首先介绍一下 animation-name
,通过 animation-name
,CSS 引擎将会找到对应的 @keyframes 规定。
当然,它和 CSS 规定命名一样,也存在一些骚操作。譬如,他是反对 emoji 表情的,所以代码中的 animation-name
命名也能够这样写:
div {animation: 😄 3s;}
@keyframes 😄 {
0% {color: #f00;}
100% {color: #000;}
}
而 animation-duration
设置动画一个周期的时长,上述 DEMO 中,就是设定动画整体继续 3s
,这个也十分好了解。
animation-delay 详解
animation-delay
就比拟有意思了,它能够设置动画延时,即从元素加载实现之后到动画序列开始执行的这段时间。
简略的一个 DEMO:
<div></div>
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation-name: move;
animation-duration: 2s;
}
div:nth-child(2) {animation-delay: 1s;}
@keyframes move {
0% {transform: translate(0);
}
100% {transform: translate(200px);
}
}
比拟下列两个动画,一个增加了 animation-delay
,一个没有,十分直观:
上述第二个 div,对于 animation
属性,也能够简写为 animation: move 2s 1s
,第一个工夫值示意持续时间,第二个工夫值示意延迟时间。
animation-delay 能够为负值
对于 animation-delay
,最有意思的技巧在于,它能够是正数。也就是说,尽管属性名是 动画延迟时间 ,然而使用了正数之后,动画能够 提前进行。
假如咱们要实现这样一个 loading 动画成果:
有几种思路:
- 初始 3 个球的地位就是距离 120°,同时开始旋转,然而这样代码量会略微多一点
- 另外一种思路,同一个动画,3 个元素的其中两个提早整个动画的 1/3,2/3 工夫登程
计划 2 的外围伪代码如下:
.item:nth-child(1) {animation: rotate 3s infinite linear;}
.item:nth-child(2) {animation: rotate 3s infinite 1s linear;}
.item:nth-child(3) {animation: rotate 3s infinite 2s linear;}
然而,在动画的前 2s,另外两个元素是不会动的,只有 2s 过后,整个动画才是咱们想要的:
此时,咱们能够让第 2、3 个元素的延迟时间,改为负值,这样能够让动画提早进行 -1s
、-2s
,也就是提前进行 1s
、2s
:
.item:nth-child(1) {animation: rotate 3s infinite linear;}
.item:nth-child(2) {animation: rotate 3s infinite -1s linear;}
.item:nth-child(3) {animation: rotate 3s infinite -2s linear;}
这样,每个元素都无需期待,间接就是静止状态中的,并且元素距离地位是咱们想要的后果:
利用 animation-duration 和 animation-delay 构建随机成果
还有一个有意思的小技巧。
同一个动画,咱们利用肯定范畴内随机的 animation-duration
和肯定范畴内随机的 animation-delay
,能够无效的构建更为随机的动画成果,让动画更加的天然。
我在下述两个纯 CSS 动画中,都应用了这样的技巧:
- 纯 CSS 实现华为充电动画:
- 纯 CSS 实现火焰动画:
以 纯 CSS 实现华为充电动画 为例子,简略解说一下。
仔细观察这一部分,回升的一个一个圆球,抛去这里的一些交融成果,只关注一直回升的圆球,看着像是没有什么法则可言:
咱们来模仿一下,如果是应用 10 个 animation-duration
和 animation-delay
都统一的圆的话,外围伪代码:
<ul>
<li></li>
<!-- 共 10 个...-->
<li></li>
</ul>
ul {
display: flex;
flex-wrap: nowrap;
gap: 5px;
}
li {
background: #000;
animation: move 3s infinite 1s linear;
}
@keyframes move {
0% {transform: translate(0, 0);
}
100% {transform: translate(0, -100px);
}
}
这样,小球的运动会是这样的整齐划一:
要让小球的静止显得十分的随机,只须要让 animation-duration
和 animation-delay
都在肯定范畴内浮动即可,革新下 CSS:
@for $i from 1 to 11 {li:nth-child(#{$i}) {animation-duration: #{random(2000)/1000 + 2}s;
animation-delay: #{random(1000)/1000 + 1}s;
}
}
咱们利用 SASS 的循环和 random()
函数,让 animation-duration
在 2-4 秒范畴内随机,让 animation-delay
在 1-2 秒范畴内随机,这样,咱们就能够失去十分天然且不同的回升动画成果,根本不会呈现反复的画面,很好的模仿了随机成果:
CodePen Demo — 利用范畴随机 animation-duration 和 animation-delay 实现随机动画成果
animation-timing-function 缓动函数
缓动函数在动画中十分重要,它定义了动画在每一动画周期中执行的节奏。
缓动次要分为两类:
- cubic-bezier-timing-function 三次贝塞尔曲线缓动函数
- step-timing-function 步骤缓动函数(这个翻译是我本人翻的,可能有点奇怪)
三次贝塞尔曲线缓动函数
首先先看看 三次贝塞尔曲线缓动函数。在 CSS 中,反对一些缓动函数关键字。
/* Keyword values */
animation-timing-function: ease; // 动画以低速开始,而后放慢,在完结前变慢
animation-timing-function: ease-in; // 动画以低速开始
animation-timing-function: ease-out; // 动画以低速完结
animation-timing-function: ease-in-out; // 动画以低速开始和完结
animation-timing-function: linear; // 匀速,动画从头到尾的速度是雷同的
对于它们之间的成果比照:
除了 CSS 反对的这 5 个关键字,咱们还能够应用 cubic-bezier()
办法自定义三次贝塞尔曲线:
animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
这里有个十分好用的网站 — cubic-bezier 用于创立和调试生成不同的贝塞尔曲线参数。
三次贝塞尔曲线缓动对动画的影响
对于缓动函数对动画的影响,这里有一个十分好的示例。这里咱们应用了纯 CSS 实现了一个钟的成果,对于其中的动画的静止,如果是 animation-timing-function: linear
,成果如下:
而如果咱们我把缓动函数替换一下,变成 animation-timing-function: cubic-bezier(1,-0.21,.85,1.29)
,它的曲线对应如下:
整个钟的动画律动成果将变成这样,齐全不一样的感觉:
CodePen Demo – 缓动不同成果不同
对于许多精益求精的动画,在设计中其实都思考到了缓动函数。我很久之前看到过一篇《基于物理学的动画用户体验设计》,惋惜现在曾经无奈找到原文。其中传播出的一些概念是,动画的设计根据理论在生活中的体现去考量。
譬如 linear 这个缓动,理论利用于某些动画中会显得很不天然,因为因为空气阻力的存在,程序模仿的匀速直线静止在现实生活中是很难实现的。因而对于这样一个用户平时很少感知到的静止是很难建设信任感的。这样的匀速直线静止也是咱们在进行动效设计时须要竭力防止的。
步骤缓动函数
接下来再讲讲步骤缓动函数。在 CSS 的 animation-timing-function
中,它有如下几种体现状态:
{
/* Keyword values */
animation-timing-function: step-start;
animation-timing-function: step-end;
/* Function values */
animation-timing-function: steps(6, start)
animation-timing-function: steps(4, end);
}
在 CSS 中,应用步骤缓动函数最多的,就是利用其来实现逐帧动画。假如咱们有这样一张图(图片大小为 1536 x 256
,图片来源于网络):
能够发现它其实是一个人物前进过程中的 6 种状态,或者能够为 6 帧,咱们利用 animation-timing-function: steps(6)
能够将其用一个 CSS 动画串联起来,代码十分的简略:
<div class="box"></div>
.box {
width: 256px;
height: 256px;
background: url('https://github.com/iamalperen/playground/blob/main/SpriteSheetAnimation/sprite.png?raw=true');
animation: sprite .6s steps(6, end) infinite;
}
@keyframes sprite {
0% {background-position: 0 0;}
100% {background-position: -1536px 0;}
}
简略解释一下上述代码,首先要晓得,刚好 256 x 6 = 1536
,所以上述图片其实能够刚好均分为 6 段:
- 咱们设定了一个大小都为
256px
的 div,给这个 div 赋予了一个animation: sprite .6s steps(6) infinite
动画; - 其中
steps(6)
的意思就是将设定的 @keyframes 动画分为 6 次(6 帧)执行,而整体的动画工夫是0.6s
,所以每一帧的进展时长为0.1s
; - 动画成果是由
background-position: 0 0
到background-position: -1536px 0
,因为上述的 CSS 代码没有设置background-repeat
,所以其实background-position: 0 0
是等价于background-position: -1536px 0
,就是图片在整个动画过程中推动了一轮,只不过每一帧停在了特点的中央,一共 6 帧;
将上述 1、2、3,3 个步骤画在图上简略示意:
从上图可知,其实在动画过程中,background-position
的取值其实只有 background-position: 0 0
,background-position: -256px 0
,background-position: -512px 0
顺次类推始终到 background-position: -1536px 0
,因为背景的 repeat 的个性,其实刚好回到原点,由此又从新开始新一轮同样的动画。
所以,整个动画就会是这样,每一帧停留 0.1s 后切换到下一帧(留神这里是个有限循环动画),:
残缺的代码你能够戳这里 — CodePen Demo — Sprite Animation with steps()
animation-duration 动画长短对动画的影响
在这里再插入一个小章节,animation-duration
动画长短对动画的影响也是非常明显的。
在上述代码的根底上,咱们再批改 animation-duration
,缩短每一帧的工夫就能够让步行的成果变成跑步的成果,同理,也能够减少每一帧的停留时间。让每一步变得迟缓,就像是在步行一样。
须要提出的是,上文说的每一帧,和浏览器渲染过程中的 FPS 的每一帧不是同一个概念。
看看成果,设置不同的 animation-duration
的成果(这里是 0.6s -> 0.2s),GIF 录屏失落了一些关键帧,实际效果会更好点:
当然,在 steps()
中,还有 steps(6, start)
和 steps(6, end)
的差别,也就是其中关键字 start
和 end
的差别。对于上述的有限动画而言,其实根本是能够忽略不计的,它次要是管制动画第一帧的开始和继续时长,比拟小的一个知识点然而想讲明确须要比拟长的篇幅,限于本文的内容,在这里不做开展,读者能够自行理解。
同个动画成果的补间动画和逐帧动画演绎比照
上述的三次贝塞尔曲线缓动和步骤缓动,其实就是对应的补间动画和逐帧动画。
对于同个动画而言,有的时候两种缓动都是实用的。咱们在具体应用的时候须要具体分析选取。
假如咱们用 CSS 实现了这样一个图形:
当初想利用这个图形制作一个 Loading 成果,如果利用补间动画,也就是三次贝塞尔曲线缓动的话,让它旋转起来,失去的成果十分的个别:
.g-container{animation: rotate 2s linear infinite;}
@keyframes rotate {
0% {transform: rotate(0);
}
100% {transform: rotate(360deg);
}
}
动画成果如下:
然而如果这里,咱们将补间动画换成逐帧动画,因为有 20 个点,所以设置成 steps(20),再看看成果,会失去齐全不一样的感觉:
.g-container{animation: rotate 2s steps(20) infinite;
}
@keyframes rotate {
0% {transform: rotate(0);
}
100% {transform: rotate(360deg);
}
}
动画成果如下:
整个 loading 的圈圈看上去如同也在旋转,实际上只是 20 帧关键帧在切换,整体的成果感觉更适宜 Loading 的成果。
因而,两种动画成果都是很有必要把握的,在理论应用的时候灵便尝试,抉择更适宜的。
上述 DEMO 成果残缺的代码:CodePen Demo — Scale Loading steps vs linear
animation-play-state
接下来,咱们讲讲 animation-play-state
,顾名思义,它能够管制动画的状态 — 运行或者暂停。相似于视频播放器的开始和暂停。是 CSS 动画中无限的管制动画状态的伎俩之一。
它的取值只有两个(默认为 running):
{animation-play-state: paused | running;}
应用起来也非常简单,看上面这个例子,咱们在 hover 按钮的时候,实现动画的暂停:
<div class="btn stop">stop</div>
<div class="animation"></div>
.animation {
width: 100px;
height: 100px;
background: deeppink;
animation: move 2s linear infinite alternate;
}
@keyframes move {
100% {transform: translate(100px, 0);
}
}
.stop:hover ~ .animation {animation-play-state: paused;}
一个简略的 CSS 动画,然而当咱们 hover 按钮的时候,给动画元素增加上 animation-play-state: paused
:
animation-play-state 小技巧,默认暂停,点击运行
失常而言,依照失常思路应用 animation-play-state: paused
是非常简单的。
然而,如果咱们想发明一些有意思的 CSS 动画成果,不如反其道而行之。
咱们都晓得,失常状况下,动画应该是运行状态,那如果咱们将一些动画的默认状态设置为暂停,只有当鼠标点击或者 hover 的时候,才设置其 animation-play-state: running
,这样就能够失去很多乏味的 CSS 成果。
看个倒酒的例子,这是一个纯 CSS 动画,然而默认状态下,动画处于 animation-play-state: paused
,也就是暂停状态,只有当鼠标点击杯子的时,才设置 animation-play-state: running
,让酒倒下,利用 animation-play-state
实现了一个十分有意思的交互成果:
残缺的 DEMO 你能够戳这里:CodePen Demo — CSS Beer!
在十分多 Web 创意交互动画咱们都能够看到这个技巧的身影。
- 页面 render 后,无任何操作,动画不会开始。只有当鼠标对元素进行
click
,通过触发元素的:active
伪类成果的时候,赋予动画animation-play-state: running
,动画才开始进行; - 动画进行到任意时刻,鼠标进行点击,伪类隐没,则动画进行;
animation-fill-mode 管制元素在各个阶段的状态
下一个属性 animation-fill-mode
,很多人会误认为它只是用于管制元素在动画完结后是否复位。这个其实是不精确的,不全面的。
看看它的取值:
{
// 默认值,当动画未执行时,动画将不会将任何款式利用于指标,而是应用赋予给该元素的 CSS 规定来显示该元素的状态
animation-fill-mode: none;
// 动画将在利用于指标时立刻利用第一个关键帧中定义的值,并在 `animation-delay` 期间保留此值,animation-fill-mode: backwards;
// 指标将保留由执行期间遇到的最初一个关键帧计算值。最初一个关键帧取决于 `animation-direction` 和 `animation-iteration-count`
animation-fill-mode: forwards;
// 动画将遵循 `forwards` 和 `backwards` 的规定,从而在两个方向上扩大动画属性
animation-fill-mode: both;
}
对于 animation-fill-mode
的解读,我在 Segment Fault 上的一个问答中(SF – 如何了解 animation-fill-mode)看到了 4 副很好的解读图,这里借用一下:
假如 HTML 如下:
<div class="box"></div>
CSS 如下:
.box{transform: translateY(0);
}
.box.on{animation: move 1s;}
@keyframes move{from{transform: translateY(-50px)}
to {transform: translateY( 50px)}
}
应用图片来示意 translateY
的值与 工夫 的关系:
- 横轴为示意 工夫,为 0 时示意动画开始的工夫,也就是向 box 加上 on 类名的工夫,横轴一格示意 0.5s
- 纵轴示意
translateY
的值,为 0 时示意translateY
的值为 0,纵轴一格示意50px
animation-fill-mode: none
体现如图:
一句话总结,元素在动画工夫之外,款式只受到它的 CSS 规定限度,与 @keyframes 内的关键帧定义无关。
animation-fill-mode: backwards
体现如图:
一句话总结,元素在动画开始之前(蕴含未触发动画阶段及 animation-delay
期间)的款式为动画运行时的第一帧,而动画完结后的款式则复原为 CSS 规定设定的款式。
animation-fill-mode: forwards
体现如图:
一句话总结,元素在动画开始之前的款式为 CSS 规定设定的款式,而动画完结后的款式则体现为由执行期间遇到的最初一个关键帧计算值(也就是停在最初一帧)。
animation-fill-mode: both
体现如图:
一句话总结,综合了 animation-fill-mode: backwards
和 animation-fill-mode: forwards
的设定。动画开始前的款式为动画运行时的第一帧,动画完结后停在最初一帧。
animation-iteration-count/animation-direction 动画循环次数和方向
讲到了 animation-fill-mode
,咱们就能够顺带讲讲这个两个比拟好了解的属性 — animation-iteration-count
和 animation-direction
animation-iteration-count
管制动画运行的次数,能够是数字或者infinite
,留神,数字能够是小数animation-direction
管制动画的方向,正向、反向、正向交替与反向交替
在下面讲述 animation-fill-mode
时,我应用了 动画运行时的第一帧 代替了 @keyframes 中定义的第一帧这种说法,因为动画运行的第一帧和最初一帧的理论状态还会受到动画运行方向 animation-direction
和 animation-iteration-count
的影响。
在 CSS 动画中,由 animation-iteration-count
和 animation-direction
独特决定动画运行时的第一帧和最初一帧的状态。
- 动画运行的第一帧由
animation-direction
决定 - 动画运行的最初一帧由
animation-iteration-count
和animation-direction
决定
动画的最初一帧,也就是动画运行的最终状态,并且咱们能够利用 animation-fill-mode: forwards
让动画在完结后停留在这一帧,这个还是比拟好了解的,然而 animation-fill-mode: backwards
和 animation-direction
的关系很容易弄不分明,这里简答解说下。
设置一个 100px x 100px
的滑块,在一个 400px x 100px
的容器中,其代码如下:
<div class="g-father">
<div class="g-box"></div>
</div>
.g-father {
width: 400px;
height: 100px;
border: 1px solid #000;
}
.g-box {
width: 100px;
height: 100px;
background: #333;
}
体现如下:
那么,退出 animation
之后,在不同的 animation-iteration-count
和 animation-direction
作用下,动画的初始和完结状态都不一样。
如果设置了 animation-fill-mode: backwards
,则元素在动画未开始前的状态由 animation-direction
决定:
.g-box {
...
animation: move 4s linear;
animation-play-state: paused;
transform: translate(0, 0);
}
@keyframes move {
0% {transform: translate(100px, 0);
}
100% {transform: translate(300px, 0);
}
}
留神这里 CSS 规定中,元素没有设置位移 transform: translate(0, 0)
,而在动画中,第一个关键帧和最初一个要害的 translateX 别离是 100px
、300px
,配合不同的 animation-direction
初始状态如下。
下图假如咱们设置了动画默认是暂停的 — animation-play-state: paused
,那么动画在开始前的状态为:
动画的分治与复用
讲完了每一个属性,咱们再来看看一些动画应用过程中的细节。
看这样一个动画:
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation: combine 2s;
}
@keyframes combine {
100% {transform: translate(0, 150px);
opacity: 0;
}
}
这里咱们实现了一个 div 块着落动画,着落的同时产生透明度的变动:
对于这样一个多个属性变动的动画,它其实等价于:
div {animation: falldown 2s, fadeIn 2s;}
@keyframes falldown {
100% {transform: translate(0, 150px);
}
}
@keyframes fadeIn {
100% {opacity: 0;}
}
在 CSS 动画规定中,animation
是能够接管多个动画的,这样做的目标不仅仅只是为了 复用 ,同时也是为了 分治,咱们对每一个属性层面的动画可能有着更为准确的管制。
keyframes 规定的设定
咱们常常可能在各种不同的 CSS 代码见到如下两种 CSS @keyframes
的设定:
-
应用百分比
@keyframes fadeIn { 0% {opacity: 1;} 100% {opacity: 0;} }
-
应用
from
及to
@keyframes fadeIn { from {opacity: 1;} to {opacity: 0;} }
在 CSS 动画 @keyframes
的定义中,from
等同于 0%
,而 to
等同于 100%
。
当然,当咱们的关键帧不止 2 帧的时,更举荐应用百分比定义的形式。
除此之外,当动画的起始帧等同于 CSS 规定中赋予的值并且没有设定 animation-fill-mode
,0%
和 from
这一帧是能够删除的。
动画状态的高优先级性
我已经在这篇文章中 — 深刻了解 CSS(Cascading Style Sheets)中的层叠(Cascading)讲过一个很有意思的 CSS 景象。
这也是很多人对 CSS 优先级的一个认知误区,在 CSS 中,优先级还须要思考选择器的层叠(级联)程序。
只有在层叠程序相等时,应用哪个值才取决于款式的优先级。
那什么是层叠程序呢?
依据 CSS Cascading 4 最新规范:
CSS Cascading and Inheritance Level 5(Current Work)
定义的以后标准下申明的层叠程序优先级如下(越往下的优先级越高,上面的规定按升序排列):
- Normal user agent declarations
- Normal user declarations
- Normal author declarations
- Animation declarations
- Important author declarations
- Important user declarations
- Important user agent declarations
- Transition declarations
简略翻译一下:
依照上述算法,大略是这样:
过渡动画过程中每一帧的款式 > 用户代理、用户、页面作者设置的!important 款式 > 动画过程中每一帧的款式优先级 > 页面作者、用户、用户代理一般款式。
然而,通过多个浏览器的测试,实际上并不是这样。(难堪了)
举个例子,咱们能够通过这个个性,笼罩掉行内款式中的 !important
款式:
<p class="txt" style="color:red!important">123456789</p>
.txt {animation: colorGreen 2s infinite;}
@keyframes colorGreen {
0%,
100% {color: green;}
}
在 Safari 浏览器下,上述 DEMO 文本的色彩为 绿色,也就是说,处于动画状态中的款式,可能笼罩掉行内款式中的 !important
款式,属于最最高优先级的一种款式,咱们能够通过有限动画、或者 animation-fill-mode: forwards
,利用这个技巧,笼罩掉原本应该是优先级十分十分高的行内款式中的 !important
款式。
我在早两年的 Chrome 中也能失去同样的后果,然而到明天(2022-01-10),最新版的 Chrome 曾经不反对动画过程中关键帧款式优先级笼罩行内款式 !important
的个性。
对于不同浏览器,感兴趣的同学能够利用我这个 DEMO 自行尝试,CodePen Demo – the priority of CSS Animation
CSS 动画的优化
这也是十分多人十分关怀的一个重点。
我的 CSS 动画很卡,我应该如何去优化它?
动画元素生成独立的 GraphicsLayer,强制开始 GPU 减速
CSS 动画很卡,其实是一个景象形容,它的实质其实是在动画过程中,浏览器刷新渲染页面的帧率过低。通常而言,目前大多数浏览器刷新率为 60 次 / 秒,所以通常来讲 FPS 为 60 frame/s 时动画成果较好,也就是每帧的耗费工夫为 16.67ms。
页面处于动画变动时,当帧率低于肯定数值时,咱们就感觉到页面的卡顿。
而造成帧率低的起因就是浏览器在一帧之间解决的事件太多了,超过了 16.67ms,要优化每一帧的工夫,又须要残缺地晓得浏览器在每一帧干了什么,这个就又波及到了陈词滥调的浏览器渲染页面。
到明天,尽管不同浏览器的渲染过程不完全相同,然而基本上大同小异,基本上都是:
简化一下也就是这个图:
这两张图,你能够在十分多不同的文章中看到。
回归本文的重点,Web 动画很大一部分开销在于层的重绘,以层为根底的复合模型对渲染性能有着深远的影响。当不须要绘制时,复合操作的开销能够忽略不计,因而在试着调试渲染性能问题时,首要指标就是要防止层的重绘。那么这就给动画的性能优化提供了方向,缩小元素的重绘与回流。
这其中,如何缩小页面的回流与重绘呢,这里就会使用到咱们常说的 GPU 减速。
GPU 减速的实质其实是缩小浏览器渲染页面每一帧过程中的 reflow 和 repaint,其基本,就是让须要进行动画的元素,生成本人的 GraphicsLayer。
浏览器渲染一个页面时,它应用了许多没有裸露给开发者的两头表现形式,其中最重要的构造便是层(layer)。
在 Chrome 中,存在有不同类型的层:RenderLayer(负责 DOM 子树),GraphicsLayer(负责 RenderLayer 的子树)。
GraphicsLayer,它对于咱们的 Web 动画而言十分重要,通常,Chrome 会将一个层的内容在作为纹理上传到 GPU 前先绘制 (paint) 进一个位图中。如果内容不会扭转,那么就没有必要重绘 (repaint) 层。
而当元素生成了本人的 GraphicsLayer 之后,在动画过程中,Chrome 并不会始终重绘整个层,它会尝试智能地去重绘 DOM 中生效的局部,也就是产生动画的局部,在 Composite 之前,页面是处于一种分层状态,借助 GPU,浏览器仅仅在每一帧对生成了本人独立 GraphicsLayer 元素层进行重绘,如此,大大的升高了整个页面重排重绘的开销,晋升了页面渲染的效率。
因而,CSS 动画(Web 动画同理)优化的第一条准则就是 让须要动画的元素生成了本人独立的 GraphicsLayer,强制开始 GPU 减速,而咱们须要晓得是,GPU 减速的实质是利用让元素生成了本人独立的 GraphicsLayer,升高了页面在渲染过程中重绘重排的开销。
当然,生成本人的独立的 GraphicsLayer,不仅仅只有 transform3d api,还有十分多的形式。在 CSS 中,包含但不限于(找了很多文档,没有很全面的,须要一个一个去尝试,通过开启 Chrome 的 Layer border 选项):
- 3D 或透视变换(perspective、transform) CSS 属性
- 应用减速视频解码的 <video> 元素
- 领有 3D (WebGL) 上下文或减速的 2D 上下文的 <canvas> 元素
- 混合插件(如 Flash)
- 对本人的 opacity 做 CSS 动画或应用一个动画变换的元素
- 领有减速 CSS 过滤器的元素
- 元素有一个蕴含复合层的后辈节点(换句话说,就是一个元素领有一个子元素,该子元素在本人的层里)
- 元素有一个 z-index 较低且蕴含一个复合层的兄弟元素(换句话说就是该元素在复合层下面渲染)
对于上述一大段十分绕的内容,你能够再看看这几篇文章:
- 【Web 动画】CSS3 3D 行星运行 && 浏览器渲染原理
- Accelerated Rendering in Chrome
除了上述准则之外,还有一些晋升 CSS 动画性能的倡议:
缩小应用耗性能款式
不同款式在耗费性能方面是不同的,扭转一些属性的开销比扭转其余属性要多,因而更可能使动画卡顿。
例如,与扭转元素的文本色彩相比,扭转元素的 box-shadow
将须要开销大很多的绘图操作。box-shadow
属性,从渲染角度来讲非常耗性能,起因就是与其余款式相比,它们的绘制代码执行工夫过长。这就是说,如果一个耗性能重大的款式常常须要重绘,那么你就会遇到性能问题。
相似的还有 CSS 3D 变换、mix-blend-mode
、filter
,这些款式相比其余一些简略的操作,会更加的耗费性能。咱们应该尽可能的在动画过程中升高其应用的频率或者寻找代替计划。
当然,没有不变的事件,在明天性能很差的款式,可能今天就被优化,并且浏览器之间也存在差别。
因而关键在于,咱们须要针对每一起卡顿的例子,借助开发工具来分辨出性能瓶颈所在,而后设法缩小浏览器的工作量。学会 Chrome 开发者工具的 Performance 面板及其他渲染相干的面板十分重要,当然这不是本文的重点。大家能够自行摸索。
应用 will-change 进步页面滚动、动画等渲染性能
will-change
为 Web 开发者提供了一种告知浏览器该元素会有哪些变动的办法,这样浏览器能够在元素属性真正发生变化之前提前做好对应的优化筹备工作。这种优化能够将一部分简单的计算工作提前准备好,使页面的反馈更为疾速灵活。
值得注意的是,用好这个属性并不是很容易:
- 不要将
will-change
利用到太多元素上:浏览器曾经尽力尝试去优化所有能够优化的货色了。有一些更强力的优化,如果与 will-change 联合在一起的话,有可能会耗费很多机器资源,如果适度应用的话,可能导致页面响应迟缓或者耗费十分多的资源。 - 有节制地应用:通常,当元素复原到初始状态时,浏览器会抛弃掉之前做的优化工作。然而如果间接在样式表中显式申明了
will-change
属性,则示意指标元素可能会常常变动,浏览器会将优化工作保留得比之前更久。所以最佳实际是当元素变动之前和之后通过脚本来切换will-change
的值。 - 不要过早利用
will-change
优化:如果你的页面在性能方面没什么问题,则不要增加will-change
属性来榨取一丁点的速度。will-change
的设计初衷是作为最初的优化伎俩,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。适度应用will-change
会导致大量的内存占用,并会导致更简单的渲染过程,因为浏览器会试图筹备可能存在的变动过程。这会导致更重大的性能问题。 - 给它足够的工作工夫:这个属性是用来让页面开发者告知浏览器哪些属性可能会变动的。而后浏览器能够抉择在变动产生前提前去做一些优化工作。所以给浏览器一点工夫去真正做这些优化工作是十分重要的。应用时须要尝试去找到一些办法提前肯定工夫获知元素可能产生的变动,而后为它加上
will-change
属性。
有人说 will-change
是良药,也有人说是毒药,在具体应用的时候,能够多测试一下。
最初
好了,本文从多个方面,由浅入深地形容了 CSS 动画我认为的一些比拟重要、值得一讲、须要留神的点。当然很多中央点到即止,或者限于篇幅没有齐全开展,很多细节还须要读者进一步浏览标准或者自行尝试验证,实际出真知,纸上得来终觉浅。
OK,本文到此结束,心愿本文对你有所帮忙 :)
想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 — iCSS 前端趣闻 😄
更多精彩 CSS 技术文章汇总在我的 Github — iCSS,继续更新,欢送点个 star 订阅珍藏。
如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。