前端实现序列帧_CSS技巧:逐帧动画抖动解决方案

本文探讨了在使用rem单位进行移动端适配时,前端实现序列帧动画遇到的抖动问题,并通过分析原因提出了三种解决方案:避免使用雪碧图、利用CSS transform scale和结合SVG的foreignObject标签,重点介绍了SVG方案的优越性和兼容性。
摘要由CSDN通过智能技术生成

笔者所在的前端团队主要从事移动端的H5页面开发,而团队使用的适配方案是: viewport units + rem。具体可以参见凹凸实验室的文章 – 利用视口单位实现适配布局 。

笔者目前(2017.08.12)接触到的移动端适配方案中,「利用视口单位实现适配布局」是最好的方案。不过使用 rem 作为单位会遇到以下两个难点:

微观尺寸(20px左右)定位不准

逐帧动画容易有抖动

第一个难点的通常出现在 icon 绘制过程,可以使用图片或者 svg-icon 解决这个问题,笔者强烈建议使用 svg-icon,具体理由可以参见:「拥抱Web设计新趋势:SVG Sprites实践应用」。

第二个难点笔者举个例子来分析抖动的原因和寻找解决方案。

一个抖动的例子

做一个8帧的逐帧动画,每帧的尺寸为:360×540。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

.steps_anim {

position: absolute;

width: 9rem;

height: 13.5rem;

background: url(//misc.aotu.io/leeenx/sprite/m.png) 0 0 no-repeat;

background-size: 45rem 13.5rem;

top: 50%;

left: 50%;

margin: –5.625rem 0 0 –5.625rem;

animation: step 1.2s steps(5) infinite;

}

@keyframes step {

100% {

background-position: –45rem;

}

}

观察在主流(手机)分辨率下的播放情况:

iPhone 6

(375×667)

iPhone 6+

(414×736)

iPhone 5

(320×568)

Android

(360×640)

68780cccec654e5f37721ea1a03e6ea0.gif

e1d868261100034c0638c5995d4c59fe.gif

d0a667a166e1d56700cd76facfe42c76.gif

8b1842bdb3156828ddda31993febbf93.gif

四种分辨率下,可以看到除了 ip6 其它的三种分辨率都发生了抖动。(ip6 不抖动的原因是适配方案是基本于 ip6 的分辨率订制的。)

分析抖动

图像由终端(屏幕)显示,而终端则是一个个光点(物理像素)组成的矩阵,换句话说图片也一组光点矩阵。为了方便描述,笔者假设终端上的一个光点代表css中的1px。

以下是一张 9px * 3px 的sprite:

f79bd624b1a39d767100979310c24e7e.png

每帧的尺寸为 3px * 3px,逐帧的取位过程如下:

230108c0a576f397f1f765ed52285832.gif

把 sprite 的 background-size 的宽度取一半,那么终端会怎么处理?

9 / 2 = 4.5

终端的光点都是以自然数的形式出现的,这里需要做取整处理。取整一般是三种方式:round/ceil/floor。假设是 round ,那么 background-size: 5px,sprite 会是以下三种的一个:

情况一

情况二

情况三

8fe4594c02de57d4c1c46fb1b15abc7d.png

94dd19558504a765391877ba070d2d61.png

56bac8f56938457af04d6b74cb6b79b9.png

理论上,5 / 3 = 1.666...。但实际上光点取整后,三个帧的宽度都不可能等于 1.666...,而是有一个帧的宽度降级为 1px(亏),另外两个宽度升级为 2px(盈),笔者把这个现象称作「盈亏互补」。

再看一下盈亏互补后,逐帧的取位过程:

情况一

情况二

情况三

6ad1e1d0a59b06607e04bf88b8505f09.gif

f7493843ae44aca4ce5819b6676dd423.gif

5c055fb5d8cfed8d02af75d901b43901.gif

可以看到由于盈亏互补导致了三个帧的宽度不一致,亏的那一帧在动画中的表示就是抖动。

笔者总结抖动的原因是:sprite在尺寸缩放后,帧与帧之间的盈亏互补现象导致动画抖动

附注:1px 由几个光点表示是由以终端的 dpr 决定

解决方案

「盈亏互补」也可以说是「盈亏不一致」,如果尺寸在缩放后「盈亏一致」那么抖动现象可以解决。

解决构想一

笔者根据「盈亏一致」设计了「解决构想一」:

892db1cbc902184eddef219ba5aa3382.gif

根据上图,其实很容易就联想到一个简单的方案:不用雪碧图(即一帧对应一张图片)。

这个方案确实是可以解决抖问题,不过笔者并不推荐使用它,因为它有两个负面的东西:

KB变大与请求数增多

多余的 animation 代码

这个方案很简单,这里就不赘述了。

解决构想二

把逐帧取位与图像缩放拆分成两个独立的过程,就是笔者的「解决构想二」:

9c7949322dc425368591056623c984f0.gif

实现「构想二」,笔者首先想到的是使用 transform: scale(),于是整理了一个实现方案A:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

.steps_anim {

position: absolute;

width: 360px;

height: 540px;

background: url(//misc.aotu.io/leeenx/sprite/m.png) 0 0 no-repeat;

background-size: 1800px 540px;

top: 50%;

left: 50%;

transform-origin: left top;

margin: –5.625rem 0 0 –5.625rem;

transform: scale(.5);

animation: step 1.2s steps(5) infinite;

}

@keyframes step {

100% {

background-position: –1800px;

}

}

/* 写断点 */

@media screen and (width: 320px) {

.steps_anim {

transform: scale(0.4266666667);

}

}

@media screen and (width: 360px) {

.steps_anim {

transform: scale(0.48);

}

}

@media screen and (width: 414px) {

.steps_anim {

transform: scale(0.552);

}

}

这个实现方案A存在明显的缺陷:scale 的值需要写很多断点代码。于是笔者结全一段 js 代码来改善这个实现方案B:

css:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

.steps_anim {

position: absolute;

width: 360px;

height: 540px;

background: url(“//misc.aotu.io/leeenx/sprite/m.png”) 0 0 no-repeat;

background-size: 1800 540px;

top: 50%;

left: 50%;

transform-origin: left top;

margin: –5.625rem 0 0 –5.625rem;

animation: step 1.2s steps(5) infinite;

}

@keyframes step {

100% {

background-position: –1800px;

}

}

javascript:

1

2

3

4

5

6

7

8

9

// 以下代码放到

document.write(“”);

function doResize(){

scaleStyleSheet.innerHTML = “.steps_anim {-webkit-transform: scale(“ + (document.documentElement.clientWidth / 750) + “)}”;

}

window.onresize = doResize;

doResize();

通过改善后的方案 CSS 的断点没了,感觉是不错了,不过笔者觉得这个方案不是个纯粹的构建方案。

我们知道  是可以根据指定的尺寸自适应缩放尺寸的,如果逐帧动画也能与  自适应缩放,那就可以从纯构建角度实现「构想二」。

SVG刚好可以解决难题!!!SVG 的表现与  类似同时可以做动画。以下是笔者的实现方案C。

html:

1

2

3

css:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

.steps_anim {

position: absolute;

width: 9rem;

height: 13.5rem;

top: 50%;

left: 50%;

margin: –5.625rem 0 0 –5.625rem;

image {

animation: step 1.2s steps(5) infinite;

}

}

@keyframes step {

100% {

transform: translate3d(-1800px, 0, 0);

}

}

方案C的改良

实现方案C很好地解决了方案A和方案B的缺陷,不过方案C也有它的问题:不利于自动化工具去处理图片。

自动化工具一般是怎么处理图片的?

自动化工具一般是扫描 CSS 文件找出所有的 url(...) 语句,然后再处理这些语句指向的图片文件。

如果  可以改用 CSS 的 background-image 就可以解决这个问题,不过 SVG 不支持 CSS 的 background-image。但是,SVG有一个扩展标签:foreignObject,它允许向  插入 html 代码。在使用它前,先看一下它的兼容情况:

cfb01009f23b78b6b33e0746e8697fae.png

iOS 与 Android 4.3 一片草绿兼容情况算是良好,笔者实机测试腾讯 X5 内核的浏览器兼容仍旧良好。以下是改良后的方案。

html:

1

2

3

4

5

css:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

.steps_anim {

position: absolute;

width: 9rem;

height: 13.5rem;

top: 50%;

left: 50%;

margin: –5.625rem 0 0 –5.625rem;

}

.html {

width: 360px;

height: 540px;

}

.img {

width: 1800px;

height: 540px;

background: url(//misc.aotu.io/leeenx/sprite/m.png) 0 0 no-repeat;

background-size: 1800px 540px;

animation: step 1.2s steps(5) infinite;

}

@keyframes step {

100% {

background-position: –1800px 0;

}

}

结语

感谢阅读完本文章的读者。本文是笔者的个人观点,希望能帮助到有相关问题的朋友,如果本文有不妥之处请不吝赐教。

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值