CSS 也具备原生的动画实现机制,可以在不使用或少使用 JS 的情况下做到动画的效果
概述
CSS 动画是基于对盒模型的变形、变色等 2D变换 实现的
也就是说,其核心是 transform 属性
一般有三种方案
- 定时器
- transition 过渡
- animation 动画
显然动画是逐帧播放的,每一帧都依赖于浏览器渲染。于是先介绍一下浏览器渲染
浏览器渲染
关于浏览器渲染的详细优化,可以查阅 渲染性能
首先介绍一个关键词:像素管道
像素管道
如图,这就是像素管道,表示渲染的全过程
- JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的
animate
函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。 - 样式计算。此过程是根据匹配选择器(例如
.headline
或.nav > .nav__item
)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。 - 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如
<body>
元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。 - 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
- 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。
并非每次渲染都会触发全部的 5 个过程,大致分为如下 3 种
JS / CSS > 样式 > 布局 > 绘制 > 合成
当修改了元素的几何属性时,会触发重新布局,所以 5 个过程都会执行
JS / CSS > 样式 > 绘制 > 合成
如果只是修改了背景图片、文字颜色等仅依赖绘制的属性,即不会影响布局,则浏览器会跳过布局,但仍将执行绘制
JS / CSS > 样式 > 合成
如果更改的属性是一个既不需要布局也不需要绘制的属性,则浏览器将直接跳到合成
第 3 种情况的开销最小,最适合于应用生命周期中的高压力点,例如动画或滚动
布局
首次布局,称之为 布局(layout)
之后的修改布局,称之为 回流 或 重排(reflow)
重排,即元素的几何属性被修改,导致浏览器重新执行整个渲染流程
由上图情况 1 可得,重排必定引发重绘
重绘
首次绘制,称之为 绘制(paint)
之后的修改表现,称之为 重绘(repaint)
重绘,即背景图片、文字颜色等仅依赖绘制的属性被修改,导致浏览器重新绘制所需的颜色等
由上图情况 1 和 2 可得,引发重绘的时候不一定发生重排
合成
已经算完了,只要同步到页面上就可以了,这就是合成,所以不能跳过
查询
那怎么知道什么属性会不会引发重排或重绘呢?可以查看 CSS Triggers
测试
Chrome 提供了工具,来帮助开发者确定什么时候发生了重绘
打开工具的流程如下
F12 打开开发者工具
在 HTML 和 CSS 界面,按 ESC 打开 console 界面
按 console 界面左上角的更多,打开 Rendering 面板
勾选 Paint flashing,即可观察到浏览器在每次发生重绘时,重绘的部分发绿
设置如图
也可以查看 Chrome 的文档 使用 Chrome DevTools 快速确定绘制瓶颈
定时器
可以通过定时器,为某个盒子设置定时变换
一般有三种定时器
- setTimeout
- setInterval
- requestAnimationFrame
其中 1 和 2 的用法相同,类似如下代码
1 | setTimeout(()=>{ |
setTimeout 和 setInterval 方法都会返回一个 long 整数,表示计时器 ID
可以使用 cancelTimeout(id) 或 cancelInterval(id) 来取消计时器
但是使用 setTimeout 的动画,不是统一管理动画帧,而是分别渲染的,所以会导致有时候这一帧还没渲染好,下一帧的请求又来了,会造成丢帧的问题
所以更建议使用 requestAnimationFrame 来管理动画
requestAnimationFrame 的方法原型是
1 | window.requestAnimationFrame(callback) |
要求传入一个回调函数,并在执行时向回调函数内传入一个 long 整数,表示时间戳
requestAnimationFrame 默认的执行间隔约 16.67 ms,约每秒 60 帧
可以在回调函数内通过时间戳判断是否应该准备渲染下一帧
与 setTimeout 同样的是,requestAnimationFrame 返回值也是一个 long 整数,表示计时器 ID
可以使用 cancelAnimationFrame(id) 来取消计时器
范例可以参考 MDN window.requestAnimationFrame 或 使用 requestAnimationFrame 来实现视觉变化
transition
第二种办法是使用 CSS transition 属性,令元素在两个状态之间过渡
一般用于制作悬浮等特效,呈现动画效果,而不是真正意义上的动画
查看 transition 的相关效果,可以访问 MDN transition
transition 一般有如下 4 个参数
property name(required),指示监视的属性名,当该属性在指定两个状态之间发生变化的时候触发过渡效果
可以指定该属性取值为 all,表示监视该 css 选择器选中元素的所有属性
duration(required),指示过渡动画的播出时间
delay,指示过渡效果在触发时,先延迟 delay 时长,再开始播放过渡效果
timing function,指示过渡效果的变化速率曲线,默认是 ease,可以指定为 linear(线性)
如上参数并排写,只需要用空格隔开
默认第一个出现的时间指示的是 duration,第二个才是 delay
当有多个 property 需要监视时,可以用逗号隔开参数语句
animation
第三种办法是使用 CSS animation 属性,令元素在若干个状态之间连续变化
与 transition 的区别是,transition 不能在没有触发条件的情况下播放,但 animation 不仅可以做到 transition 能做到的,还可以自动无限播放
查看 animation 的相关效果,可以访问 MDN animation
animation 一般有如下 8 个参数
- keyframe name(required),指示使用的动画帧定义
- duration(required),指示动画的播出时间
- delay,指示动画效果在触发时,先延迟 delay 时长,再开始播放动画效果
- timing function,指示动画效果的变化速率曲线,默认是 ease,可以指定为 linear(线性)
- iteration count,指示动画播出的次数,默认是 1,可以修改为 infinite
- direction,指示动画播出的方向,默认是 normal,可以修改为 reverse 反向播放,或 alternate 表示来回播放
- fill mode,指示动画播放结束时保留的帧,默认是 none,不保留任何帧,可以设置为 forwards 保留最后一帧,或 backwards 保留第一帧,或 both 在两个方向上扩展动画
- play state,指示一个动画所处的状态,默认是 running,可以通过 JS 设置为 paused 来暂停动画,重新修改为 running 则会在暂停的位置继续播放
显然,animation 属性规定了动画在播放时的参数,但还需要另外指定关键帧
在 transition 中是根据不同的动作触发,在 animation 中则是用 @keyframes 关键字定义
@keyframes 一般有如下两种写法
from-to写法
1
2
3
4
5
6
7
8
9@keyframes slidein {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}让 animation 在起始和末尾的两个帧之间变化
百分比写法
1
2
3
4
5
6
7
8@keyframes identifier {
0% { top: 0; left: 0; }
30% { top: 50px; }
50% { top: 30px; left: 20px; }
50% { top: 10px; }
68%, 72% { left: 50px; }
100% { top: 100px; left: 100%; }
}让动画在生命周期的不同节点呈现不同的效果
相同节点重复定义,会以最后一次定义为准,如第 4 和第 5 行,只有第 5 行会生效
相同效果可以用逗号隔开,如第 6 行
以上,就是 CSS 动画相关的效果
关于优化等问题,需要配合 JS,此处就不描述了
谢谢阅读