逐帧动画抖动、适配布局、SVG Sprites
笔者所在的前端团队主要从事移动端的H5页面开发,而团队使用的适配方案是: viewport units + rem
。具体可以参见凹凸实验室的文章 – 利用视口单位实现适配布局 。
笔者目前(2017.08.12)接触到的移动端适配方案中,「利用视口单位实现适配布局」是最好的方案。不过使用 rem
作为单位会遇到以下两个难点:
- 微观尺寸(20px左右)定位不准
- 逐帧动画容易有抖动
第一个难点的通常出现在 icon
绘制过程,可以使用图片或者 svg-icon 解决这个问题,笔者强烈建议使用 svg-icon,具体理由可以参见:「拥抱Web设计新趋势:SVG Sprites实践应用」。
第二个难点笔者举个例子来分析抖动的原因和寻找解决方案。
一个抖动的例子
做一个8帧的逐帧动画,每帧的尺寸为:360x540。
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 (375x667) |
iPhone 6+ (414x736) |
iPhone 5 (320x568) |
Android (360x640) |
---|---|---|---|
四种分辨率下,可以看到除了 ip6
其它的三种分辨率都发生了抖动。(ip6
不抖动的原因是适配方案是基本于 ip6
的分辨率订制的。)
分析抖动
图像由终端(屏幕)显示,而终端则是一个个光点(物理像素)组成的矩阵,换句话说图片也一组光点矩阵。为了方便描述,笔者假设终端上的一个光点代表css中的1px。
以下是一张 9px * 3px
的sprite:
每帧的尺寸为 3px * 3px
,逐帧的取位过程如下:
把 sprite 的 background-size 的宽度取一半,那么终端会怎么处理?
9 / 2 = 4.5
终端的光点都是以自然数的形式出现的,这里需要做取整处理。取整一般是三种方式:round/ceil/floor
。假设是 round ,那么 background-size: 5px
,sprite 会是以下三种的一个:
情况一 | 情况二 | 情况三 |
---|---|---|
理论上,5 / 3 = 1.666...
。但实际上光点取整后,三个帧的宽度都不可能等于 1.666...
,而是有一个帧的宽度降级为 1px
(亏),另外两个宽度升级为 2px
(盈),笔者把这个现象称作「盈亏互补」。
再看一下盈亏互补后,逐帧的取位过程:
情况一 | 情况二 | 情况三 |
---|---|---|
可以看到由于盈亏互补导致了三个帧的宽度不一致,亏的那一帧在动画中的表示就是抖动。
笔者总结抖动的原因是:sprite在尺寸缩放后,帧与帧之间的盈亏互补现象导致动画抖动
附注:1px 由几个光点表示是由以终端的 dpr 决定
解决方案
「盈亏互补」也可以说是「盈亏不一致」,如果尺寸在缩放后「盈亏一致」那么抖动现象可以解决。
解决构想一
笔者根据「盈亏一致」设计了「解决构想一」:
根据上图,其实很容易就联想到一个简单的方案:不用雪碧图(即一帧对应一张图片)。
这个方案确实是可以解决抖问题,不过笔者并不推荐使用它,因为它有两个负面的东西:
- KB变大与请求数增多
- 多余的 animation 代码
这个方案很简单,这里就不赘述了。
解决构想二
把逐帧取位与图像缩放拆分成两个独立的过程,就是笔者的「解决构想二」:
实现「构想二」,笔者首先想到的是使用 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
|
// 以下代码放到 <head></head> 中
<script>
document.write("<style id='scaleStyleSheet'>.steps_anim {scale(.5); }</style>");
function doResize() {
scaleStyleSheet.innerHTML = ".steps_anim {-webkit-transform: scale(" + (document.documentElement.clientWidth / 750) + ")}";
}
window.onresize = doResize;
doResize();
</script>
|
通过改善后的方案 CSS 的断点没了,感觉是不错了,不过笔者觉得这个方案不是个纯粹的构建方案。
我们知道 <img>
是可以根据指定的尺寸自适应缩放尺寸的,如果逐帧动画也能与 <img>
自适应缩放,那就可以从纯构建角度实现「构想二」。
SVG
刚好可以解决难题!!!SVG
的表现与 <img>
类似同时可以做动画。以下是笔者的实现方案C。
html:
1
2
3
|
<svg viewBox="0, 0, 360, 540" class="steps_anim">
<image xlink:href="//misc.aotu.io/leeenx/sprite/m.png" width="1800" height="540" />
</svg>
|
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(...)
语句,然后再处理这些语句指向的图片文件。
如果 <image>
可以改用 CSS 的 background-image
就可以解决这个问题,不过 SVG
不支持 CSS 的 background-image
。但是,SVG
有一个扩展标签:foreignObject
,它允许向 <svg></svg>
插入 html
代码。在使用它前,先看一下它的兼容情况:
iOS 与 Android 4.3 一片草绿兼容情况算是良好,笔者实机测试腾讯 X5
内核的浏览器兼容仍旧良好。以下是改良后的方案。
html:
1
2
3
4
5
|
<svg viewBox="0, 0, 360, 540" class="steps_anim">
<foreignObject class="html" width="360" height="540">
<div class="img"></div>
</foreignObject>
</svg>
|
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;
}
}
|
改良后的方案DEMO: http://jdc.jd.com/fd/promote/leeenx/201708/svg-sprite.html
结语
感谢阅读完本文章的读者。本文是笔者的个人观点,希望能帮助到有相关问题的朋友,如果本文有不妥之处请不吝赐教。
参考资料:
https://stackoverflow.com/questions/9946604/insert-html-code-inside-svg-text-element
https://www.w3.org/TR/SVG/extend.html
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject
逐帧动画抖动、适配布局、SVG Sprites的更多相关文章
- CSS技巧:逐帧动画抖动解决方案
笔者所在的前端团队主要从事移动端的H5页面开发,而团队使用的适配方案是: viewport units + rem.具体可以参见凹凸实验室的文章 – 利用视口单位实现适配布局 . 笔者目前(2017. ...
- Android动画效果之Frame Animation(逐帧动画)
前言: 上一篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画),今天来总结下Android的另外一种动画Frame ...
- Android中的动画具体解释系列【1】——逐帧动画
逐帧动画事实上非常easy,以下我们来看一个样例: <?xml version="1.0" encoding="utf-8"?> <anima ...
- Android简单逐帧动画Frame的实现(三)
android之动画(三)通过AnimationDrawable控制逐帧动画 android与逐帧动画: 效果图: 当我们点击按钮时,该图片会不停的旋转,当再次点击按钮时,会停止在当前的状态. ...
- Android简单逐帧动画Frame的实现(二)
Android简单逐帧动画Frame的实现 Android简单逐帧动画Frame的实现 1.逐帧动画 即是通过播放预先排序好的图片来实现动态的画面,感觉像是放电影. 2.实现步骤: 1. 在工程里 ...
- 转Android 用Animation-list实现逐帧动画
Android 用Animation-list实现逐帧动画 第一步:先上图片素材,以下素材放到res/drawable目录下: http://blog.csdn.net/aminfo/arti ...
- Android中的动画详解系列【1】——逐帧动画
逐帧动画其实很简单,下面我们来看一个例子: <?xml version="1.0" encoding="utf-8"?> <animation ...
- Android中实现一个简单的逐帧动画(附代码下载)
场景 Android中的逐帧动画,就是由连续的一张张照片组成的动画. 效果 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 ...
- Android 逐帧动画
原理: 逐帧动画是最简单的一种动画.原理就是把几张图片连续显示出来,以达到动画的效果.就相当于下面这种手绘翻页动画啦~ 实现: 1.需要建立一个animation-list来设置静态图片资源.持续时间 ...
随机推荐
- nginx 1.14.2 配置文件优化精选
user nobody; worker_processes ; worker_rlimit_nofile ; events { use epoll; worker_connections ; } ht ...
- vscode技巧之代码规范editorconfig
使用代码片段一键初始化editorconfig样式 第一步,选择文件 第二步,在首选项中选择代码片段 第三步,搜索框中键入properties,并enter键选择进入该文件 第四步,键入下面的代码 { ...
- J2EE笔记
J2EE的优势 J2EE允许公司把一些通用的.很繁琐的服务端任务交给中间件供应商去完成.这样开发人员可以集中精力在如何创建商业逻辑上,相应地缩短了开发时间.高级中间件供应商提供以下这些复杂的中间件服务 ...
- xml转json和实体类的两种方式
本文为博主原创,未经允许不得转载: xml在http通信中具有较高的安全性和传输速度,所以应用比较广泛, 在项目中往往需要对xml,json和实体类进行相互转换,在这里总结一下自己所用到的一些方法: ...
- Clover 安装 Mac 系统更新 (原版黑苹果)
关于使用原版镜像(即 .dmg )安装黑苹果的升级,笔者写写自身经验吧. 在Clover启动的界面中与Mac OS有关的启动菜单有以下这些: Boot FileVault Prebooter from ...
- You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'group t1,customer t2
### SQL: select t1.gid,t1.gname,t1.gvalue,t1.gtype, t1.gaddress,t1.gmembers, t1.gcode,t1.gphone, t2. ...
- Job for docker.service failed because the control process exited with error
Docker 无法启动 报错信息:Job for docker.service failed because the control process exited with error 找了很久才解决 ...
- InstallShield Limited Edition for Visual Studio 使用
首先到https://info.flexerasoftware.com/IS-EVAL-InstallShield-Limited-Edition-Visual-Studio填写信息: 完成之后跳转到 ...
- 区块链 编译android geth 填坑记录 ubuntu
下载geth 源码 直接 make android 下载android ndk sdk 配置环境变量cd 安装golang 设置环境变量 发现没有gomobile命令 按照wiki方法 执行安装g ...
- Windows下安装Anaconda
Windows下安装Anaconda Anaconda介绍 Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项.因为包含了大量的科学 ...