CSS动画

CSS 也具备原生的动画实现机制,可以在不使用或少使用 JS 的情况下做到动画的效果

概述

CSS 动画是基于对盒模型的变形、变色等 2D变换 实现的

也就是说,其核心是 transform 属性

一般有三种方案

  1. 定时器
  2. transition 过渡
  3. animation 动画

显然动画是逐帧播放的,每一帧都依赖于浏览器渲染。于是先介绍一下浏览器渲染

浏览器渲染

关于浏览器渲染的详细优化,可以查阅 渲染性能

首先介绍一个关键词:像素管道

像素管道

如图,这就是像素管道,表示渲染的全过程

  • JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。
  • 样式计算。此过程是根据匹配选择器(例如 .headline.nav > .nav__item)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
  • 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 <body> 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
  • 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  • 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

并非每次渲染都会触发全部的 5 个过程,大致分为如下 3 种

  1. JS / CSS > 样式 > 布局 > 绘制 > 合成

    当修改了元素的几何属性时,会触发重新布局,所以 5 个过程都会执行

  2. JS / CSS > 样式 > 绘制 > 合成

    如果只是修改了背景图片、文字颜色等仅依赖绘制的属性,即不会影响布局,则浏览器会跳过布局,但仍将执行绘制

  3. JS / CSS > 样式 > 合成

    如果更改的属性是一个既不需要布局也不需要绘制的属性,则浏览器将直接跳到合成

    第 3 种情况的开销最小,最适合于应用生命周期中的高压力点,例如动画或滚动

布局

首次布局,称之为 布局(layout)

之后的修改布局,称之为 回流 或 重排(reflow)

重排,即元素的几何属性被修改,导致浏览器重新执行整个渲染流程

由上图情况 1 可得,重排必定引发重绘

重绘

首次绘制,称之为 绘制(paint)

之后的修改表现,称之为 重绘(repaint)

重绘,即背景图片、文字颜色等仅依赖绘制的属性被修改,导致浏览器重新绘制所需的颜色等

由上图情况 1 和 2 可得,引发重绘的时候不一定发生重排

合成

已经算完了,只要同步到页面上就可以了,这就是合成,所以不能跳过

查询

那怎么知道什么属性会不会引发重排或重绘呢?可以查看 CSS Triggers

测试

Chrome 提供了工具,来帮助开发者确定什么时候发生了重绘

打开工具的流程如下

  1. F12 打开开发者工具

  2. 在 HTML 和 CSS 界面,按 ESC 打开 console 界面

  3. 按 console 界面左上角的更多,打开 Rendering 面板

  4. 勾选 Paint flashing,即可观察到浏览器在每次发生重绘时,重绘的部分发绿

    设置如图

    也可以查看 Chrome 的文档 使用 Chrome DevTools 快速确定绘制瓶颈

定时器

可以通过定时器,为某个盒子设置定时变换

一般有三种定时器

  1. setTimeout
  2. setInterval
  3. requestAnimationFrame

其中 1 和 2 的用法相同,类似如下代码

1
2
3
setTimeout(()=>{
div.style.cssText='transform: translate(100px,200px)';
},1000);

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 个参数

  1. property name(required),指示监视的属性名,当该属性在指定两个状态之间发生变化的时候触发过渡效果

    可以指定该属性取值为 all,表示监视该 css 选择器选中元素的所有属性

  2. duration(required),指示过渡动画的播出时间

  3. delay,指示过渡效果在触发时,先延迟 delay 时长,再开始播放过渡效果

  4. timing function,指示过渡效果的变化速率曲线,默认是 ease,可以指定为 linear(线性)

如上参数并排写,只需要用空格隔开

默认第一个出现的时间指示的是 duration,第二个才是 delay

当有多个 property 需要监视时,可以用逗号隔开参数语句

animation

第三种办法是使用 CSS animation 属性,令元素在若干个状态之间连续变化

与 transition 的区别是,transition 不能在没有触发条件的情况下播放,但 animation 不仅可以做到 transition 能做到的,还可以自动无限播放

查看 animation 的相关效果,可以访问 MDN animation

animation 一般有如下 8 个参数

  1. keyframe name(required),指示使用的动画帧定义
  2. duration(required),指示动画的播出时间
  3. delay,指示动画效果在触发时,先延迟 delay 时长,再开始播放动画效果
  4. timing function,指示动画效果的变化速率曲线,默认是 ease,可以指定为 linear(线性)
  5. iteration count,指示动画播出的次数,默认是 1,可以修改为 infinite
  6. direction,指示动画播出的方向,默认是 normal,可以修改为 reverse 反向播放,或 alternate 表示来回播放
  7. fill mode,指示动画播放结束时保留的帧,默认是 none,不保留任何帧,可以设置为 forwards 保留最后一帧,或 backwards 保留第一帧,或 both 在两个方向上扩展动画
  8. play state,指示一个动画所处的状态,默认是 running,可以通过 JS 设置为 paused 来暂停动画,重新修改为 running 则会在暂停的位置继续播放

显然,animation 属性规定了动画在播放时的参数,但还需要另外指定关键帧

在 transition 中是根据不同的动作触发,在 animation 中则是用 @keyframes 关键字定义

@keyframes 一般有如下两种写法

  1. from-to写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @keyframes slidein {
    from {
    transform: translateX(0%);
    }

    to {
    transform: translateX(100%);
    }
    }

    让 animation 在起始和末尾的两个帧之间变化

  2. 百分比写法

    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,此处就不描述了

谢谢阅读

--It's the end.Thanks for your read.--