CSS 动画为页面创造了丰富多彩的效果和无限的可能性,并极大地提高了用户的观感体验,本文梳理一下用 CSS 实现动画效果的方法。
大体上讲,CSS 有两种可以实现动画的方法,可概括为 transition 法和 animation 法。
但是在介绍这两种方法之前,首先要了解一个非常重要的属性——transform。
一、transform
transform属性顾名思义,即对元素进行“形变”,可以实现元素的平移、缩放、旋转、倾斜等效果
(【注】transform对 inline 元素无效)。
transform常用的值有:
- translate() 平移
- scale() 缩放
- rotate() 旋转
- skew() 倾斜
translate()可以对元素进行平移。
transform: translateX(20px) /* 向右平移20px */
transform: translateY(50%) /* 向下平移自身高度的50% */
transform: translate(20px, 20px) /* 同时向右、下平移20px */
scale()对元素进行缩放。
transform: sacleX(1.2) /* 宽放大为原来的1.2倍 */
transform: sacle(1.2) /* 宽、高均放大为原来的1.2倍 */
rotate()对元素进行旋转。
transform: rotate(45deg) /* 顺时针旋转45度 */
skew()使元素发生倾斜。
transform: skew(45deg) /* 倾斜45度 */
了解了 transform 的基本用法,就可以为元素添加动画了。
二、用 transition 实现动画
transition 实际上是实现一种样式向另一种样式的过渡,因此在用 transition 实现动画前,首先要有该元素的两种不同的状态(比如 hover 和非 hover)。
在非 hover 的状态下加上 transition 属性,这样当我们鼠标悬停在元素上时,transition 会自动填充由非 hover 态样式向 hover 态样式转变的过渡动画。
例如,要实现 hover 的时候,div 旋转45度:
div{
height: 50px;
width: 50px;
border: 1px solid red;
transition: transform 1s linear;
}
div:hover{
transform: rotate(45deg);
}
当 hover 的时候,div由原来的默认样式变为顺时针旋转45度,transition 使得这两种状态的转变用1s的时间去完成,期间自动补充了过渡动画,即形成了动态的视觉效果。
transition 是一个简写属性,它的值包括属性名、持续时间、过渡方式、延迟时间,通常来说属性名和持续时间是必须的。
对属性名和过渡方式的说明:
- 属性名用以指定需要过渡的属性,all 表示所有属性
- 过渡方式是指,完成这一过渡所遵从的【进度-时间】函数关系模型,诸如匀速变化、先快后慢、先慢后快等等,CSS 提供了多种函数模型,如 linear、ease、ease-in、ease-out...
需要注意的是,并非所有的属性都支持过渡,像display: none-->display: block的变化就不支持,类似的需求可以用visibility: hidden-->visibility: visible代替。
三、用 animation 实现动画
transition 动画有一定的局限性,它只能指定两种变化状态(关键帧),即开始和结束,其中间的过程都由数学函数控制,而 animation 可以指定多个关键帧,相较而言更加灵活。
使用 animation 前首先要声明好各个关键帧,这里用到的是@keyframes语法,其写法如下
@keyframes xxx{
0% {样式一}
10% {样式二}
n% {样式三}
...
100%{样式四}
}
或
@keyframes xxx{
from {样式一}
to {样式二}
}
其中的xxx表示为该动画设定的名字。
声明好关键帧后,就可以用 animation 属性为元素应用这个动画了
div{
animation: xxx 1s infinite alternate-reverse;
}
animation 的值包括动画名、时长、过渡方式、延迟、次数、方向、填充模式、是否暂停等,具体用法参见MDN。
四、动画的性能
实现同一种动画效果,可能会有诸多方法,比如要做一个将 div 从左往右移动的动画,可以先令 div 相对定位,然后用 JS 不断改变其 left 的值:
var n = 1
var id = setInterval(() => {
if (n <= 100) {
demo.style.left = n + 'px'
n = n + 1
}else{
clearInterval(id)
}
}, 1000 / 30)
也可以用前文所述 transform + transition 法去做,表面上看效果一样,其实深究起来前者的性能会差很多,要讨论这个问题还要先从浏览器的渲染原理入手。
浏览器在渲染一个页面的时候,有以下几个步骤:
- 由 HTML 构建 HTML 树(DOM)
- 由 CSS 构建 CSS 树(CSSOM)
- 将以上两棵树合并成一棵渲染树(Render Tree)
- 进行 Layout 布局(依据文档流、盒模型确定大小、位置)
- 进行 Paint 绘制(内容、色彩)
- 进行 Compose 合成(依层叠顺序呈现出来)
实际在更新页面的时候,某些情况下可能会省略掉 Layout 过程,也可能会同时省略 Layout 和 Paint,直接进入最后的 Compose,这样就会比走完整个流程的情况性能更好。
以上第二种用 transform 的方法,就跳过了 Layout 和 Paint,直接进行 Compose,因此在性能上要好很多。
然而遗憾的是,并没有确切的规律去帮助我们判别哪些属性会跳过某个过程,只能逐个去试验,但是好在已经有人试验了所有属性,并且总结出了每个属性所触发的渲染流程,参见CSS Triggers。