CSS 动画一站式指南
CSS 动画一站式指南
目录- 在线源码和演示
2)0.5px 边框
在移动端由于屏幕的分辨率较高,因此 1px 边框看起来会有点粗,那么可以直接声明 0.5px 的边框吗?可以,但是即便声明成功了,有些浏览器还是会按照 1px 来渲染。为了实现 0.5px 的边框,我们可以利用 CSS 中的 transform 来实现。

步骤:
-
声明一个伪元素,令其宽高为 200%,border 宽度为 1px
-
通过
transform: scale(.5)将该伪元素缩小为原来的 0.5 倍。
在线源码和演示
1.2 过渡
过渡的出现让状态间的切换更加丝滑,先简单介绍一下与过渡相关的属性,相关的动画实践稍候奉上。
1.2.1 过渡属性介绍
1)transition-property:属性
-
all:全部属性过渡(默认) -
none:无属性过渡 -
String:某个属性过渡
2)transition-duration:时间
Time:秒或毫秒(默认0)
3)transition-timing-function:缓动函数
-
ease:逐渐变慢,等同于cubic-bezier(.25,.1,.25,1)(默认) -
linear:匀速,等同于cubic-bezier(0,0,1,1) -
ease-in:加速,等同于cubic-bezier(.42,0,1,1) -
ease-out:减速,等同于cubic-bezier(0,0,.58,1) -
ease-in-out:先加速后减速,等同于cubic-bezier(.42,0,.58,1) -
cubic-bezier:贝塞尔曲线,(x1,y1,x2,y2)四个值指定于曲线上的点P1和P2,所有值需在[0,1]区域内
4)transition-delay:时延
Time:秒或毫秒(默认0)
由于
duration和delay的取值都是时间,所以可能会发生混淆。-
duration和delay作用于所有节点,包括自身的::before和::after -
transition中出现两个时间值时,第一个解析为duration,第二个解析为delay -
transition中出现一个时间值时,解析为duration
变换属性中一个比较重要的属性是
transition-timing-function,它决定了过渡时间内速度是如何变化的。它的值实际上是一个贝塞尔曲线,推荐一个设置贝塞尔曲线的网站,可以根据需要设计出符合需求的贝塞尔曲线。1.2.2 过渡动画实践
1)IOS 设置页面中的开关按钮
下面这个开关在 IOS 手机的设置面板中太常见了,开关的动画效果利用
transition这个属性也可以很轻松地实现,而且这里利用上面讲到的设置贝塞尔曲线的网站调试出了一种贝塞尔曲线使得开关按钮被打开的过程有一个刹车的效果。
在线源码和演示
1.3 动画
CSS 可以通过设置多个点精确地控制一个或一组动画,用来实现复杂的动画效果。动画由多个点组成,每个点拥有独立的状态,这些状态通过浏览器处理成过渡效果,点与点之间的过渡效果串联起来就是一个完整的动画。
1.3.1 Animation 属性介绍
1)animation-name:名称
-
none:无动画(默认) -
String:动画名称
2)animation-duration:时间
Time:秒或毫秒(默认0)
3)animation-timing-function:缓动函数
-
ease:逐渐变慢,等同于cubic-bezier(.25,.1,.25,1)(默认) -
linear:匀速,等同于cubic-bezier(0,0,1,1) -
ease-in:加速,等同于cubic-bezier(.42,0,1,1) -
ease-out:减速,等同于cubic-bezier(0,0,.58,1) -
ease-in-out:先加速后减速,等同于cubic-bezier(.42,0,.58,1) -
cubic-bezier:贝塞尔曲线,(x1,y1,x2,y2)四个值指定于曲线上的点P1和P2,所有值需在[0,1]区域内 -
steps([,[start|end]]?):把动画平均划分成n等分,直到平均走完该动画 -
step-start:等同于steps(1,start),把动画分成一步,动画执行时以左侧端点0%为开始 -
step-end:等同于steps(1,end),把动画分成一步,动画执行时以右侧端点100%为开始
4)animation-delay:时延
Time:秒或毫秒(默认0)
5)animation-iteration-count:播放次数
-
Number:数值(默认1) -
infinite:无限次
6)animation-direction:轮流反向播放(播放次数为一次则该属性无效果)
-
normal:正常播放(默认) -
alternate:轮流反向播放,奇数次数正常播放,偶数次数反向播放
7)animation-play-state:播放状态
-
running:正在播放(默认) -
paused:暂停播放
8)animation-fill-mode:播放前后其效果是否可见
-
none:不改变默认行为(默认) -
backwards:在时延所指定时间内或在动画开始前应用开始属性(在第一个关键帧中定义) -
forwards:在动画结束后保持最后一个属性(在最后一个关键帧中定义) -
both:向前和向后填充模式都被应用
CSS 中动画分为两种,关键帧动画和逐帧动画。关键帧动画是将人为定义好的每一帧的状态串联成一个动画,需要通过
animation和@keyframes声明。逐帧动画的声明较为简单,使用一张逐帧长图然后配合animation-timing-function: steps()来完成。逐帧动画的声明步骤一般如下:
-
准备一张
逐帧长图,该图像包含动画效果的每一帧且每帧宽高必须一致 -
在
steps()里声明逐帧长图及其展示方式 -
在指定节点中声明
animation调用动画
关键帧动画的声明步骤一般如下:
-
在
@keyframes里声明动画名称和动画每个关键帧的状态 -
在指定节点中声明
animation调用动画
关键帧动画声明示例:
@keyframes animation-name { from {} to {} } /* 或 */ @keyframes animation-name { p1 {} p2 {} p3 {} }关键帧的取值必须是
from、to或Percentage。from可用0%代替,to可用100%代替,若开始或结束的关键帧无对应的状态,可不用声明from或to。0%的%不能省略,否则关键帧解析会失败。通过animation-fill-mode属性设置动画结束后的样式。1.3.2 Animation 动画实践
1)自动打字器
很多在线编辑器网站都有一些自动打字的效果,例如CodePen。很多同学都以为是JS实现的效果,其实查看
Chrome Devtools发现是纯CSS实现的。观察多几次自动打字器,可发现其存在以下特点。-
字体都是等宽字体,等宽字体可保证每次打字时光标的移动距离都是一致的
-
打字器的宽度由最初的
0px逐渐增加内容后变成最终固定字数的宽度,宽度以等宽字体的个数为准 -
光标随着每打一个字就闪烁一次,打字速度均匀,打字完毕再次重复打字
-
整个打字过程存在两个动画,一个是打字器自增宽度,一个是光标闪烁
-
整个打字过程一闪一闪地完成,根据其断断续续的特点可判断该动画为逐帧动画
这里用 CSS 简单实现了一个自动打字器,在线源码和演示。
2. CSS 动画综合实践(太空舱)
2.1 太空舱动画介绍
先简单介绍一下这个动画的一些背景:
-
用户点击中间的抽奖按钮后会展示抽奖动画,然后展示将要前进的步数,小人会在跑道上跑动到相应的位置,到达此次终点后,小人展示停止动画并且显示中奖结果。
-
用户还可以通过长按中奖按钮触发显示五连抽和十连抽,当点击五连抽时,首先会展示抽奖动画,然后展示将要前进的步数,小人会在跑道上瞬间移动到相应的位置,然后展示停止的动画,紧接着展示中奖动画。十连抽的过程和五连抽相同。
2.2 太空舱动画实现流程
整体流程
大体思路是将各个功能封装为类,并且控制每个类实现的功能较小,这样可以更大程度地实现类的复用。像下面介绍的
Animate CSS这个动画库也是封装了很多类,想要什么效果直接引用即可,这里也是同理。并在此基础上定义了一些函数对跑动等功能进行了封装,整体流程如下图所示。
结合上图对太空舱动画中的一些点做一些简单的说明。
跑动的实现
通过关键帧动画,将各个方向相邻两格之间的跑动封装为一个类。每一帧代表跑动的姿态,并且每一帧通过 translate 向前进的方向移动,最后一帧刚好到达下一格。
以向南方向的跑动为例。
- 向南奔跑的类
@translateSouthX和@translateSouthY代表向南每两格 X 和 Y 方向的距离,.run-south-animation是一个mixin,代表向南跑动的动画。@import "./animation.less"; @translateSouthX: -25px; @translateSouthY: 13.5px; // 向南方向跑动 .run-south { background: url("..."); background-size: 1680px 255px; .run-south-animation(@translateSouthX, @translateSouthY); }- 向南奔跑的动画
.run-south-animation这个mixin是向南奔跑动画的实现,注意animation-timing-function的值要设置为
steps(1),因为这里每两帧之间不需要补间动画。forwards表示最后的状态保持为动画结束时的状态。animation-iteration-count的值为 1 表示动画只执行一次不需要重复,因为这里是为了封装一个移动一格的动画,每次使用这个封装好的类时只应该跑动一格。关键帧
runSouth中,经过多次测试最终选择了使用 28 帧,因为在此帧数下,动画较为流畅,图片的大小也可以接受。在关键帧动画中,帧数的选取至关重要,要根据具体的需求反复测试,太多或太少都不合适。@animationDuration: .7s // 向南奔跑动画 .run-south-animation(@translateX, @translateY) { animation: runSouth @animationDuration forwards; animation-iteration-count: 1; animation-timing-function: steps(1); @keyframes runSouth { 0% { transform: translate(0, 0); background-position: 0px -2*(@singleAstronautWidth+@verticalInterval); } ... 50.4% { transform: translate(@translateX / 28 * 14, @translateY / 28 * 14); background-position: -840px -2*(@singleAstronautWidth+@verticalInterval); } ... 100% { transform: translate(@translateX / 28 * 28, @translateY / 28 * 28); background-position: -1680px -2*(@singleAstronautWidth+@verticalInterval); } } }如何再次触发跑动动画
当第一次将跑动的类(假设向南)赋予展示小人的元素时小人可以正常跑动,但是如果紧接着再将这个类赋予这个元素时,即使组件中的 state 改变了,也不会触发小人继续向南跑动了。
为了验证这一点,写了一个小 demo,如下代码所示,在 3 s 后改变 state 中的 cls 和 doc,由于 cls 没有发生变化,即使 doc 发生了变化,也不会触发
first类中的动画重新执行,在线源码和演示。class App extends React.Component { state = { cls: 'first', doc: 'hello hello' } componentDidMount() { setTimeout(() => { this.setState({ cls: 'first', doc: 'world world' }) }, 3000) } render() { return; } } ReactDOM.render({this.state.doc}
, document.getElementById("root")); body { height: 100vh; margin: 0; display: grid; place-items: center; max-width: 250px; margin: 0 auto; } .first { animation: myfirst 2s linear both; animation-iteration-count: 1; } @keyframes myfirst { from {background: red;} to {background: yellow;} }当 state 中的 doc 发生变化时,h1 元素只会更新变化的属性,并不会将整个元素重新渲染。由 react 中的 diff 算法可知,要想让 h1 元素重新渲染,可以给它赋予一个 key 值,然后在 3s 后改变这个 key 值,此时两次 key 值不同就会触发 h1 元素整个重新渲染,然后就可以让动画再次执行。
因此在 state 中新增一个 key 值,并将其赋予 h1 元素的 key 属性,在 3s 后改变这个 key 值就可以使动画重新执行。具体的代码可以参考:在线源码和演示。
移动航天小人到指定位置的实现(moveToSpecialPosByCurPos)
-
获取航天小人将要到的指定位置
-
根据将要到的指定位置获取下一步的前进方向
-
改变展示小人元素的位置(使用 transform 属性)
监听动画结束事件,来对下一步的行为做出判断
CSS 动画事件
-
animationstart 某个 CSS 动画开始时触发。
-
animationend 某个 CSS 动画完成时触发。
-
animationiteration 某个 CSS 动画完成后重新开始时触发。
前面我们封装的各个方向的类,每调用一次,小人会向前跑一步,跑完一步后会触发
animationend事件,在animationend事件的回调函数中处理下面应该如何走。-
判断是否是连抽,如果是连抽走连抽逻辑
-
如果不是连抽,将剩余的步数减 1,当前的步数加 1
-
判断当前的位置是否大于赛道的总步数,如果大于赛道总步数,将当前位置设置为起点
-
如果小人当前剩余的步数为0,那么改变ELotteryStatus的状态触发中奖动画的展示,并且触发小人停止时的发光动画,在1s后隐藏小人的发光动画。
-
如果小人当前的剩余步数不为0,根据当前的位置获取下一步的前进方向,然后将相应前进方向跑动动画的类赋予给展示小人的元素
-
改变展示小人元素的 key 值触发再次执行动画,小人继续向前跑动。
连抽时瞬间移动的实现
-
当前位置加上抽奖的步数得到最终的位置
-
最终的位置是否超过赛道长度,如果超过了赛道长度,利用最终的位置减去赛道的总长度
-
调用 moveToSpecialPosByCurPos 函数移动到指定位置
-
触发小人停止时的发光动画,并且在 1s 后隐藏小人的发光动画,改变 ELotteryStatus 状态展示中动画
-
从后台拉取最新的位置,校正小人的位置
3. CSS 动画库
在了解 CSS 动画的原理之后,实际做动画需求的时候,一种比较推荐的做法是站在巨人的肩膀上。业界已经有很多优秀的开源动画库了,可以看一下这些开源的动画库是否可以用得上。
这里主要介绍两个比较常用的 CSS 动画库
Animista和Animate CSS。Animista提供了很多动画类型,并且还提供了很多参数供你设置,设置完成之后就可以获得该动画的 CSS 代码了。Animate CSS则是提供了很多动画类,通过给元素设置不同的类就可以实现不同的动画效果。3.1 Animista
Animista 是一个在线动画生成器,同时也是一个动画库,基本使用步骤如下:
1)选择动画,调节参数
首先选择你想要的动画类型,然后根据你的需求对该动画的参数进行调节。

2)生成CSS代码
设置完满足自己需求的动画后,我们可以直接从网站上获取代码,甚者还可以进行压缩。

3.2 Animate CSS
Animate CSS 是最著名的动画库之一,接下来介绍一下
Animate CSS的一些基本用法,详细的用法可以去官网查询。基本用法
首先在需要添加动画的元素上添加类
animated,然后是动画的名字。Animate CSS ****还提供了一些类来控制动画的一些属性,例如动画的延迟和速度等。
-
delay通过添加
delay类来延迟动画的播放。
-