web 动画形式
首先我们来了解一下 Web 有哪些动画形式
1\. CSS3 动画
- Transform(变形)
- Transition(过渡)
- Animation(动画)
2\. JS 动画 (操作 DOM, 修改 CSS 属性值)
3\. Canvas 动画
4\. SVG 动画
5\. 以 Three.JS 为首的 3D 动画
以上各种动画形式都可以制作出一种类型的动画, 那就是帧动画, 也叫序列帧动画, 定格动画, 逐帧动画等, 这里我们统一用帧动画来表述.
这里推荐一下我的前端学习交流扣 qun:767273102 , 里面都是学习前端的从最基础的 html+CSS+JS[炫酷特效, 游戏, 插件封装, 设计模式] 到移动端 HTML5 的项目实战的学习资料都有整理, 送给每一位前端小伙伴. 2019 最新技术, 与企业需求同步. 好友都在里面学习交流, 每天都会有大牛定时讲解前端技术!
点击: 加入 http://u6.gg/srMpX
应用场景
帧动画一般用来实现稍微复杂一点的动画效果, 同时希望动画更细腻, 设计师更自由的发挥. 他可以定义到每一个时间刻度上的展现内容, 我们一般用帧动画来做页面的 Loading, 小人物, 小物体元素的简单动画. 我们想象中的帧动画应该有以下几个特点:
可以自由控制播放, 暂停和停止
可以控制播放次数, 播放速度
可以添加交互, 在播放完成后添加事件
浏览器兼容性好
素材准备
帧动画的素材一般是先由设计师在 PS 中的时间轴上设计好了, 然后导出图片给前端人员, PS 制作时间轴动画一般是用来制作稍微简单的动画, 操作简单, 方便.
或者是由设计师在 AE 的时间轴进行设计, 因为 AE 内置了更丰富的动作效果, 比如转换, 翻转之类的, AE 可以帮助我们实现更复杂的效果, 然后再导出图片给前端人员.
这里帧动画素材的要求, 每一帧的图片最好是偶数宽高, 偶数张, 最好周围能有一些留白.
实现方案
将目前想到的解决方案梳理如下图, 同时我们将对每种方案进行详细介绍.
一, GIF 图
我们可以将上面制作的帧动画导出成 GIF 图, GIF 图会连续播放, 无法暂停, 它往往用来实现小细节动画, 成本较低, 使用方便. 但其缺点也是很明显的:
画质上, gif 支持颜色少 (最大 256 色),Alpha 透明度支持差, 图像锯齿毛边比较严重;
交互上, 不能直接控制播放, 暂停, 播放次数, 灵活性差;
性能上, gif 会引起页面周期性的绘画, 性能较差.
二, CSS3 帧动画
CSS3 帧动画是我们今天需要重点介绍的方案, 最核心的是利用 CSS3 中 Animation 动画, 确切的说是使用 animation-timing-function 的阶梯函数 steps(number_of_steps, direction) 来实现逐帧动画的连续播放.
帧动画的实现原理是不断切换视觉内图片内容, 利用视觉滞留生理现象来实现连续播放的动画效果, 下面我们来介绍制作 CSS3 帧动画的几种方案.
(1) 连续切换动画图片地址 src(不推荐)
我们将图片放到元素的背景中 (background-image), 通过更改 background-image 的值实现帧的切换. 但是这种方式会有以下几个缺点, 所以该方案不推荐.
多张图片会带来多个 HTTP 请求
每张图片首次加载会造成图片切换时的闪烁
不利于文件的管理
(2) 连续切换雪碧图位置 (推荐)
我们将所有的帧动画图片合并成一张雪碧图, 通过改变 background-position 的值来实现动画帧切换. 分两步进行:
步骤一: 将动画帧合并为雪碧图, 雪碧图的要求可以看上面素材准备, 比如下面这张帧动画雪碧图, 共 20 帧.
[图片上传失败...(image-91f7b7-1556627487319)]
<figcaption></figcaption>
步骤二: 使用 steps 阶梯函数切换雪碧图位置
先看写法一:
- <div class="sprite"></div>
- .sprite {
- width: 300px;
- height: 300px;
- background-repeat: no-repeat;
- background-image: url(frame.PNG);
- animation: frame 333ms steps(1,end) both infinite;
- }
- @keyframes frame {
- 0% {background-position: 0 0;}
- 5% {background-position: -300px 0;}
- 10% {background-position: -600px 0;}
- 15% {background-position: -900px 0;}
- 20% {background-position: -1200px 0;}
- 25% {background-position: -1500px 0;}
- 30% {background-position: -1800px 0;}
- 35% {background-position: -2100px 0;}
- 40% {background-position: -2400px 0;}
- 45% {background-position: -2700px 0;}
- 50% {background-position: -3000px 0;}
- 55% {background-position: -3300px 0;}
- 60% {background-position: -3600px 0;}
- 65% {background-position: -3900px 0;}
- 70% {background-position: -4200px 0;}
- 75% {background-position: -4500px 0;}
- 80% {background-position: -4800px 0;}
- 85% {background-position: -5100px 0;}
- 90% {background-position: -5400px 0;}
- 95% {background-position: -5700px 0;}
- 100% {background-position: -6000px 0;}
- }
针对以上动画有疑问?
问题一: 既然都详细定义关键帧了, 是不是可以不用 steps 函数了, 直接定义 linear 变化不就好了吗?
animation: frame 10s linear both infinite;
如果我们定义成这样, 动画是不会阶梯状, 一步一步执行的, 而是会连续的变化背景图位置, 是移动的效果, 而不是切换的效果, 如下图:
问题二: 不是应该设置为 20 步吗, 怎么变成了 1?
这里我们先来了解下 animation-timing-function 属性.
CSS animation-timing-function 属性定义 CSS 动画在每一动画周期中执行的节奏. 对于关键帧动画来说, timing function 作用于一个关键帧周期而非整个动画周期, 即从关键帧开始开始, 到关键帧结束结束.
timing-function 作用于每两个关键帧之间, 而不是整个动画.
接着我们来了解下 steps() 函数:
steps 函数指定了一个阶跃函数, 它接受两个参数.
第一个参数接受一个整数值, 表示两个关键帧之间分几步完成.
第二个参数有两个值 < start> or <end>. 默认值为 < end> .
step-start 等同于 step(1, start).step-end 等同于 step(1, end).
综上我们可以知道, 因为我们详细定义了一个关键帧周期, 从开始到结束, 每两个关键帧之间分 1 步展示完, 也就是说 0% ~ 5% 之间变化一次, 5% ~ 10% 变化一次, 所以我们这样写才能达到想要的效果.
再看写法二:
- <div class="sprite"></div>
- .sprite {
- width: 300px;
- height: 300px;
- background-repeat: no-repeat;
- background-image: url(frame.PNG);
- animation: frame 333ms steps(20) both infinite;
- }
- @keyframes frame {
- 0% {background-position: 0 0;}// 可省略
- 100% {background-position: -6000px 0;}
- }
这里我们定义了关键帧的开始和结束, 也就是定义了一个关键帧周期, 但因为我们没有详细的定义每一帧的展示, 所以我们要将 0%~100% 这个区间分成 20 步来阶段性展示.
也可以换成关键字的写法, 还可以只定义最后一帧, 因为默认第一帧就是初始位置.
- @keyframes frame {
- from {background-position: 0 0;}// 可省略
- to {background-position: -6000px 0;}
- }
(3) 连续移动雪碧图位置 (移动端推荐)
跟第二种基本一致, 只是切换雪碧图的位置过程换成了 transform:translate3d() 来实现, 不过要加多一层 overflow: hidden; 的容器包裹, 这里我们以只定义初始和结束帧为例, 使用 transform 可以开启 GPU 加速, 提高机器渲染效果, 还能有效解决移动端帧动画抖动的问题.
- <div class="sprite-wp">
- <div class="sprite"></div>
- </div>
- .sprite-wp {
- width: 300px;
- height: 300px;
- overflow: hidden;
- }
- .sprite {
- width: 6000px;
- height: 300px;
- will-change: transform;
- background: url(frame.PNG) no-repeat center;
- animation: frame 333ms steps(20) both infinite;
- }
- @keyframes frame {
- 0% {transform: translate3d(0,0,0);}
- 100% {transform: translate3d(-6000px,0,0);}
- }
三, JS 帧动画
(1) 通过 JS 来控制 img 的 src 属性切换 (不推荐)
和上面 CSS3 帧动画里面切换元素 background-image 属性一样, 会存在多个请求等问题, 所以该方案我们不推荐, 但是这是一种解决思路.
(2) 通过 JS 来控制 Canvas 图像绘制
通过 Canvas 制作帧动画的原理是用 drawImage 方法将图片绘制到 Canvas 上, 不断擦除和重绘就能得到我们想要的效果.
- <canvas id="canvas" width="300" height="300"></canvas>
- (function () {
- var timer = null,
- canvas = document.getElementById("canvas"),
- context = canvas.getContext('2d'),
- img = new Image(),
- width = 300,
- height = 300,
- k = 20,
- i = 0;
- img.src = "frame.png";
- function drawImg() {
- context.clearRect(0, 0, width, height);
- i++;
- if (i == k) {
- i = 0;
- }
- context.drawImage(img, i * width, 0, width, height, 0, 0, width, height);
- Windows.requestAnimationFrame(drawImg);
- }
- img.onload = function () {
- Windows.requestAnimationFrame(drawImg);
- }
- })();
上面是通过改变裁剪图像的 X 坐标位置来实现动画效果的, 也可以通过改变画布上放置图像的坐标位置实现, 如下: context.drawImage(img, 0, 0, width*k, height,-i*width,0,width*k,height);.
(3) 通过 JS 来控制 CSS 属性值变化
这种方式和前面 CSS3 帧动画一样, 有三种方式, 一种是通过 JS 切换元素背景图片地址 background-image, 一种是通过 JS 切换元素背景图片定位 background-position, 最后一种是通过 JS 移动元素 transform:translate3d(), 第一种不做介绍, 因为同样会存在多个请求等问题, 不推荐使用, 这里实现后面两种.
切换元素背景图片位置 background-position
- .sprite {
- width: 300px;
- height: 300px;
- background: url(frame.PNG) no-repeat 0 0;
- }
- <div class="sprite" id="sprite"></div>
- (function(){
- var sprite = document.getElementById("sprite"),
- picWidth = 300,
- k = 20,
- i = 0,
- timer = null;
- // 重置背景图片位置
- sprite.style = "background-position: 0 0";
- // 改变背景图位置
- function changePosition(){
- sprite.style = "background-position:"+(-picWidth*i)+"px 0";
- i++;
- if(i == k){
- i = 0;
- }
- Windows.requestAnimationFrame(changePosition);
- }
- Windows.requestAnimationFrame(changePosition);
- })();
移动元素背景图片位置
- transform:translate3d()
- .sprite-wp {
- width: 300px;
- height: 300px;
- overflow: hidden;
- }
- .sprite {
- width: 6000px;
- height: 300px;
- will-change: transform;
- background: url(frame.PNG) no-repeat center;
- }
- <div class="sprite-wp">
- <div class="sprite" id="sprite"></div>
- </div>
- (function () {
- var sprite = document.getElementById("sprite"),
- picWidth = 300,
- k = 20,
- i = 0,
- timer = null;
- // 重置背景图片位置
- sprite.style = "transform: translate3d(0,0,0)";
- // 改变背景图移动
- function changePosition() {
- sprite.style = "transform: translate3d(" + (-picWidth * i) + "px,0,0)";
- i++;
- if (i == k) {
- i = 0;
- }
- Windows.requestAnimationFrame(changePosition);
- }
- Windows.requestAnimationFrame(changePosition);
- })();
方案总结
总结以上几种方案, 我们可以看到 GIF 图有一定的优点同时缺点和局限性也比较明显, 所以这种方案看情况选择使用.
其他实现方案的性能如何呢, 我们来比较一下, 如果测试结果出现偏差, 可能与测试环境变化有关.
测试环境:
系统: Windows 10 专业版
处理器: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41GHz
RAM: 8.00GB
浏览器: Chrome 72.0
CSS transform:translate3d() 方案性能数据
如上图, 我们通过 Chrome 浏览器的各种工具, 查看了每种方案的 FPS,CPU 占用率, GPU 占用, Scripting,Rendering,Painting, 内存的使用情况, 得到以下数据:
性能 - 方案 | cssbackground-position | css
| JS Canvas | JSbackground-position | JS
|
---|---|---|---|---|---|
FPS | 60 | 51 | 60 | 60 | 60 |
CPU | 5%-6.2% | 0.3%-1% | 7%-8% | 6%-8% | 6%-8% |
GPU | 3.8MB | 4-10MB | 0 | 3.8MB | 4-11MB |
Scripting | 0 | 0 | 2.51% | 2.61% | 3.18% |
Rendering | 1.17% | 0.141% | 0.84% | 1.65% | 2.71% |
Painting | 1.58% | 0.01% | 1.63% | 1.75% | 1.05% |
内存 | 20112K | 21120K | 21588K | 20756K | 21576K |
通过分析以上数据我们可以得出以下几点:
除了 CSS
transform:translate3d()
方案, 其他方案的 FPS 都能达到 60FPS 的流畅程度, 但该方案的 FPS 也不是很低.
CPU 占用率最低的方案是 CSS
transform:translate3d()
方案.
GPU 占用最低的方案是 JS Canvas 绘制方案.
CSS 方案没有脚本开销
Rendering 最少的是 CSS
transform:translate3d()
方案.
Painting 最少的是 CSS
transform:translate3d()
方案.
各方案内存占用区别不大.
结论: 我们看到, 在 7 个指标中, CSS transform:translate3d() 方案将其中的 4 个指标做到了最低, 从这点看, 我们完全有理由选择这种方案来实现 CSS 帧动画.
至于其他方案的绝对比较暂时没法给出结论, 看具体情况来选择, 也看开发者对哪个性能指标的追求.
延伸来看我们的 Web 动画, 每种形式的动画都有其各自的有点, 比如大量的粒子效果用 Canvas 绘制方案肯定要比 DOM+CSS 实现要好的, 大量的 CSS 属性值变换, 使用 transform 实现性能是要更好的.
注意事项
素材: 动画图片宽高最好是偶数, 总帧数最好是偶数, 图片拼接处最好有一定的留白.
适配: 移动端适配最好不用 rem, 因为 rem 的计算会造成小数四舍五入, 造成一定的抖动效果, 建议直接用 px 作为单位, 同时辅助以 scale(zoom) 媒体查询进行适配. 如果使用 rem 适配, 试试使用 transform 的方案, 抖动问题可以得到优化解决.
tips: 使用 will-change 可以在元素属性真正发生变化之前提前做好对应准备.
总结
本文我们主要梳理了目前实现帧动画的几种方案, 同时对各种方案进行效果实现, 优劣讨论, 性能对比, 同时简单介绍了帧动画实现过程的注意事项, 最后我们得出结论, CSS transform:translate3d() 方案在实现和性能上都明显优于其他方案.
来源: http://www.bubuko.com/infodetail-3042613.html