| 笔者所在的前端团队主要从事移动端的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 
        |  
        
        .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)
 |   
   |  |  |  |  |  四种分辨率下,可以看到除了 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 
        
         10 
        
         11 
        
         12 
        
         13 
        |  
        
        // 以下代码放到<head></head>中// <![CDATA[ 
         
        document.write(" 
        
           
         
        .steps_anim {scale(.5); } 
        
           
        
           
         
        "); 
         
        function doResize() { 
         
          scaleStyleSheet.innerHTML = ".steps_anim {-webkit-transform: scale(" + (document.documentElement.clientWidth / 750) + ")}"; 
         
        } 
         
        window.onresize = doResize; 
         
        doResize(); 
         
        // ]]> 
        |  通过改善后的方案 CSS 的断点没了,感觉是不错了,不过笔者觉得这个方案不是个纯粹的构建方案。 我们知道<img> 是可以根据指定的尺寸自适应缩放尺寸的,如果逐帧动画也能与 <img>自适应缩放,那就可以从纯构建角度实现「构想二」。 SVG刚好可以解决难题!!!SVG的表现与 <img>类似同时可以做动画。以下是笔者的实现方案C。
 html:  
  
 
     
   
   
    
     
     |  |  
        
        <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 
        |  
        
        .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代码。在使用它前,先看一下它的兼容情况: 
 iOS 与 Android 4.3 一片草绿兼容情况算是良好,笔者实机测试腾讯 X5内核的浏览器兼容仍旧良好。以下是改良后的方案。 html:  
  
 
     
   
   
    
     
     |  |  
        
        <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 
        |  
        
        .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-elementhttps://www.w3.org/TR/SVG/extend.html
 https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject
 |