深刻浅出 CSS 动画

2022年01月15日 阅读数:6
这篇文章主要向大家介绍深刻浅出 CSS 动画,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

本文将比较全面细致的梳理一下 CSS 动画的方方面面,针对每一个属性用法的讲解及进阶用法的示意,但愿能成为一个比较好的从入门到进阶的教程。css

CSS 动画介绍及语法

首先,咱们来简单介绍一下 CSS 动画。html

最新版本的 CSS 动画由规范 -- CSS Animations Level 1 定义。html5

CSS 动画用于实现元素从一个 CSS 样式配置转换到另外一个 CSS 样式配置。git

动画包括两个部分: 描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。github

简单来讲,看下面的例子:算法

div {
    animation: change 3s;
}

@keyframes change {
    0% {
        color: #f00;
    }
    100% {
        color: #000;
    }
}
  1. animation: move 1s 部分就是动画的第一部分,用于描述动画的各个规则;
  2. @keyframes move {} 部分就是动画的第二部分,用于指定动画开始、结束以及中间点样式的关键帧;

一个 CSS 动画必定要由上述两部分组成。segmentfault

CSS 动画的语法

接下来,咱们简单看看 CSS 动画的语法。api

建立动画序列,须要使用 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-nameanimation-duration@keyframes规则
  • 非必须项animation-delayanimation-directionanimation-iteration-countanimation-play-stateanimation-timing-functionanimation-fill-mode,固然不是说它们不重要,只是不设置时,它们都有默认值

上面已经给了一个简单的 DEMO, 就用上述的 DEMO,看看结果:

这就是一个最基本的 CSS 动画,本文将从 animation 的各个子属性入手,探究 CSS 动画的方方面面。

animation-name / animation-duration 详解

总体而言,单个的 animation-nameanimation-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 动画效果:

有几种思路:

  1. 初始 3 个球的位置就是间隔 120°,同时开始旋转,可是这样代码量会稍微多一点
  2. 另一种思路,同一个动画,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 事后,整个动画才是咱们想要的:

此时,咱们可让第 二、3 个元素的延迟时间,改成负值,这样可让动画延迟进行 -1s-2s,也就是提早进行 1s2s

.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 动画中,都使用了这样的技巧:

  1. 纯 CSS 实现华为充电动画

纯 CSS 实现华为充电动画

  1. 纯 CSS 实现火焰动画

纯 CSS 实现火焰动画

纯 CSS 实现华为充电动画为例子,简单讲解一下。

仔细观察这一部分,上升的一个一个圆球,抛去这里的一些融合效果,只关注不断上升的圆球,看着像是没有什么规律可言:

咱们来模拟一下,若是是使用 10 个 animation-durationanimation-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-durationanimation-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 缓动函数

缓动函数在动画中很是重要,它定义了动画在每一动画周期中执行的节奏。

缓动主要分为两类:

  1. cubic-bezier-timing-function 三次贝塞尔曲线缓动函数
  2. 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,效果以下:

b

而若是咱们我把缓动函数替换一下,变成 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 段:

  1. 咱们设定了一个大小都为 256px 的 div,给这个 div 赋予了一个 animation: sprite .6s steps(6) infinite 动画;
  2. 其中 steps(6) 的意思就是将设定的 @keyframes 动画分为 6 次(6帧)执行,而总体的动画时间是 0.6s,因此每一帧的停顿时长为 0.1s
  3. 动画效果是由 background-position: 0 0background-position: -1536px 0,因为上述的 CSS 代码没有设置 background-repeat,因此其实 background-position: 0 0 是等价于 background-position: -1536px 0,就是图片在整个动画过程当中推动了一轮,只不过每一帧停在了特色的地方,一共 6 帧;

将上述 一、二、3,3 个步骤画在图上简单示意:

从上图可知,其实在动画过程当中,background-position 的取值其实只有 background-position: 0 0background-position: -256px 0background-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) 的差别,也就是其中关键字 startend 的差别。对于上述的无限动画而言,其实基本是能够忽略不计的,它主要是控制动画第一帧的开始和持续时长,比较小的一个知识点可是想讲明白须要比较长的篇幅,限于本文的内容,在这里不作展开,读者能够自行了解。

同个动画效果的补间动画和逐帧动画演绎对比

上述的三次贝塞尔曲线缓动和步骤缓动,其实就是对应的补间动画和逐帧动画。

对于同个动画而言,有的时候两种缓动都是适用的。咱们在具体使用的时候须要具体分析选取。

假设咱们用 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 创意交互动画咱们均可以看到这个技巧的身影。

  1. 页面 render 后,无任何操做,动画不会开始。只有当鼠标对元素进行 click ,经过触发元素的 :active 伪类效果的时候,赋予动画 animation-play-state: running,动画才开始进行;
  2. 动画进行到任意时刻,鼠标中止点击,伪类消失,则动画中止;

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
  1. animation-fill-mode: none 表现如图:

一句话总结,元素在动画时间以外,样式只受到它的 CSS 规则限制,与 @keyframes 内的关键帧定义无关。

  1. animation-fill-mode: backwards 表现如图:

一句话总结,元素在动画开始以前(包含未触发动画阶段及 animation-delay 期间)的样式为动画运行时的第一帧,而动画结束后的样式则恢复为 CSS 规则设定的样式。

  1. animation-fill-mode: forwards 表现如图:

一句话总结,元素在动画开始以前的样式为 CSS 规则设定的样式,而动画结束后的样式则表现为由执行期间遇到的最后一个关键帧计算值(也就是停在最后一帧)。

  1. animation-fill-mode: both 表现如图:

一句话总结,综合了 animation-fill-mode: backwardsanimation-fill-mode: forwards 的设定。动画开始前的样式为动画运行时的第一帧,动画结束后停在最后一帧。

animation-iteration-count/animation-direction 动画循环次数和方向

讲到了 animation-fill-mode,咱们就能够顺带讲讲这个两个比较好理解的属性 -- animation-iteration-countanimation-direction

  • animation-iteration-count 控制动画运行的次数,能够是数字或者 infinite,注意,数字能够是小数
  • animation-direction 控制动画的方向,正向、反向、正向交替与反向交替

在上面讲述 animation-fill-mode 时,我使用了动画运行时的第一帧替代了@keyframes 中定义的第一帧这种说法,由于动画运行的第一帧和最后一帧的实际状态还会受到动画运行方向 animation-directionanimation-iteration-count 的影响。

在 CSS 动画中,由 animation-iteration-countanimation-direction 共同决定动画运行时的第一帧和最后一帧的状态。

  1. 动画运行的第一帧由 animation-direction 决定
  2. 动画运行的最后一帧由 animation-iteration-countanimation-direction 决定

动画的最后一帧,也就是动画运行的最终状态,而且咱们能够利用 animation-fill-mode: forwards 让动画在结束后停留在这一帧,这个仍是比较好理解的,可是 animation-fill-mode: backwardsanimation-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-countanimation-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 分别是 100px300px,配合不一样的 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 的设定:

  1. 使用百分比
@keyframes fadeIn {
    0% {
        opacity: 1;
    }
    100% {
        opacity: 0;
    }
}
  1. 使用 fromto
@keyframes fadeIn {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}

在 CSS 动画 @keyframes 的定义中,from 等同于 0%,而 to 等同于 100%

固然,当咱们的关键帧不止 2 帧的时,更推荐使用百分比定义的方式。

除此以外,当动画的起始帧等同于 CSS 规则中赋予的值而且没有设定 animation-fill-mode0%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 动画性能的建议:

减小使用耗性能样式

不一样样式在消耗性能方面是不一样的,改变一些属性的开销比改变其余属性要多,所以更可能使动画卡顿。

例如,与改变元素的文本颜色相比,改变元素的 box-shadow 将须要开销大不少的绘图操做。box-shadow 属性,从渲染角度来说十分耗性能,缘由就是与其余样式相比,它们的绘制代码执行时间过长。这就是说,若是一个耗性能严重的样式常常须要重绘,那么你就会遇到性能问题。

相似的还有 CSS 3D 变换、mix-blend-modefilter,这些样式相比其余一些简单的操做,会更加的消耗性能。咱们应该尽量的在动画过程当中下降其使用的频率或者寻找替代方案。

固然,没有不变的事情,在今天性能不好的样式,可能明天就被优化,而且浏览器之间也存在差别。

所以关键在于,咱们须要针对每一块儿卡顿的例子,借助开发工具来分辨出性能瓶颈所在,而后设法减小浏览器的工做量。学会 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,本文到此结束,但愿本文对你有所帮助 😃

更多精彩 CSS 技术文章汇总在个人 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

若是还有什么疑问或者建议,能够多多交流,原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。