本文将比较全面细致的梳理一下 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 实现华为充电动画为例子,简单讲解一下。
仔细观察这一部分,上升的一个一个圆球,抛去这里的一些融合效果,只关注不断上升的圆球,看着像是没有什么规律可言:
我们来模拟一下,如果是使用 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),它的曲线对应如下:
整个钟的动画律动效果将变成这样,完全不一样的感觉:
对于许多精益求精的动画,在设计中其实都考虑到了缓动函数。我很久之前看到过一篇《基于物理学的动画用户体验设计》,可惜如今已经无法找到原文。其中传达出的一些概念是,动画的设计依据实际在生活中的表现去考量。
譬如 linear 这个缓动,实际应用于某些动画中会显得很不自然,因为由于空气阻力的存在,程序模拟的匀速直线运动在现实生活中是很难实现的。因此对于这样一个用户平时很少感知到的运动是很难建立信任感的。这样的匀速直线运动也是我们在进行动效设计时需要极力避免的。