深入浅出 CSS 动画
作者:chokcoco
来源:SegmentFault 思否社区
本文将比较全面细致的梳理一下 CSS 动画的方方面面,针对每个属性用法的讲解及进阶用法的示意,希望能成为一个比较好的从入门到进阶的教程。
CSS 动画介绍及语法
首先,我们来简单介绍一下 CSS 动画。
最新版本的 CSS 动画由规范 -- CSS Animations Level 1 定义。
https://www.w3.org/TR/2018/WD-css-animations-1-20181011/
CSS 动画用于实现元素从一个 CSS 样式配置转换到另一个 CSS 样式配置。
动画包括两个部分: 描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。
简单来说,看下面的例子:
div {
animation: change 3s;
}
@keyframes change {
0% {
color: #f00;
}
100% {
color: #000;
}
}
animation: move 1s 部分就是动画的第一部分,用于描述动画的各个规则;
@keyframes move {} 部分就是动画的第二部分,用于指定动画开始、结束以及中间点样式的关键帧;
CSS 动画的语法
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,当然不是说它们不重要,只是不设置时,它们都有默认值
animation-name / animation-duration 详解
整体而言,单个的 animation-name 和 animation-duration 没有太多的技巧,非常好理解,放在一起。
div {
animation: 😄 3s;
}
@keyframes 😄 {
0% {
color: #f00;
}
100% {
color: #000;
}
}
animation-delay 详解
<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 可以为负值
初始 3 个球的位置就是间隔 120°,同时开始旋转,但是这样代码量会稍微多一点
另外一种思路,同一个动画,3 个元素的其中两个延迟整个动画的 1/3,2/3 时间出发
.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;
}
.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 构建随机效果
纯 CSS 实现华为充电动画预览:https://codepen.io/Chokcoco/pen/vYExwvm
<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);
}
}
@for $i from 1 to 11 {
li:nth-child(#{$i}) {
animation-duration: #{random(2000)/1000 + 2}s;
animation-delay: #{random(1000)/1000 + 1}s;
}
}
animation-timing-function 缓动函数
缓动函数在动画中非常重要,它定义了动画在每一动画周期中执行的节奏。
cubic-bezier-timing-function 三次贝塞尔曲线缓动函数
step-timing-function 步骤缓动函数(这个翻译是我自己翻的,可能有点奇怪)
三次贝塞尔曲线缓动函数
/* 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; // 匀速,动画从头到尾的速度是相同的
animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
三次贝塞尔曲线缓动对动画的影响
步骤缓动函数
{
/* 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);
}
<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;
}
}
我们设定了一个大小都为 256px 的 div,给这个 div 赋予了一个 animation: sprite .6s steps(6) infinite 动画;
其中 steps(6) 的意思就是将设定的 @keyframes 动画分为 6 次(6帧)执行,而整体的动画时间是 0.6s,所以每一帧的停顿时长为 0.1s;
动画效果是由 background-position: 0 0 到 background-position: -1536px 0,由于上述的 CSS 代码没有设置 background-repeat,所以其实 background-position: 0 0 是等价于 background-position: -1536px 0,就是图片在整个动画过程中推进了一轮,只不过每一帧停在了特点的地方,一共 6 帧;
animation-duration 动画长短对动画的影响
同个动画效果的补间动画和逐帧动画演绎对比
.g-container{
animation: rotate 2s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.g-container{
animation: rotate 2s steps(20) infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
animation-play-state
{
animation-play-state: paused | running;
}
<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;
}
animation-play-state 小技巧,默认暂停,点击运行
页面 render 后,无任何操作,动画不会开始。只有当鼠标对元素进行 click ,通过触发元素的 :active 伪类效果的时候,赋予动画 animation-play-state: running,动画才开始进行;
动画进行到任意时刻,鼠标停止点击,伪类消失,则动画停止;
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;
}
<div class="box"></div>
.box{
transform: translateY(0);
}
.box.on{
animation: move 1s;
}
@keyframes move{
from{transform: translateY(-50px)}
to {transform: translateY( 50px)}
}
横轴为表示 时间,为 0 时表示动画开始的时间,也就是向 box 加上 on 类名的时间,横轴一格表示 0.5s
纵轴表示 translateY 的值,为 0 时表示 translateY 的值为 0,纵轴一格表示 50px
animation-fill-mode: none 表现如图:
animation-fill-mode: backwards 表现如图:
animation-fill-mode: forwards 表现如图:
animation-fill-mode: both 表现如图:
animation-iteration-count/animation-direction 动画循环次数和方向
animation-iteration-count 控制动画运行的次数,可以是数字或者 infinite,注意,数字可以是小数
animation-direction 控制动画的方向,正向、反向、正向交替与反向交替
动画运行的第一帧由 animation-direction 决定
动画运行的最后一帧由 animation-iteration-count 和 animation-direction 决定
<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;
}
.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);
}
}
动画的分治与复用
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation: combine 2s;
}
@keyframes combine {
100% {
transform: translate(0, 150px);
opacity: 0;
}
}
div {
animation: falldown 2s, fadeIn 2s;
}
@keyframes falldown {
100% {
transform: translate(0, 150px);
}
}
@keyframes fadeIn {
100% {
opacity: 0;
}
}
keyframes 规则的设定
使用百分比
@keyframes fadeIn {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
使用 from 及 to
@keyframes fadeIn {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
动画状态的高优先级性
Normal user agent declarations
Normal user declarations
Normal author declarations
Animation declarations
Important author declarations
Important user declarations
Important user agent declarations
Transition declarations
<p class="txt" style="color:red!important">123456789</p>
.txt {
animation: colorGreen 2s infinite;
}
@keyframes colorGreen {
0%,
100% {
color: green;
}
}
CSS 动画的优化
动画元素生成独立的 GraphicsLayer,强制开始 GPU 加速
3D 或透视变换(perspective、transform) CSS 属性
使用加速视频解码的 <video> 元素
拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
混合插件(如 Flash)
对自己的 opacity 做 CSS 动画或使用一个动画变换的元素
拥有加速 CSS 过滤器的元素
元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理 https://www.cnblogs.com/coco1s/p/5439619.html Accelerated Rendering in Chrome https://www.html5rocks.com/zh/tutorials/speed/layers/#disqus_thread
减少使用耗性能样式
使用 will-change 提高页面滚动、动画等渲染性能
不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。
有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。
不要过早应用 will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。
给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change 属性。