![]()
最近碰到了一个需求, 大致是移动端有一个提示页, 在页面中会四个GIF图, 连起来像一个”小视频”一样, 用来展示商品的步骤.
但是四个GIF一起播放的话, 那么用户体验就自然没有那么好啦. 我仔细的想了想, 想到了微博的GIF图好像就是一张一张播放的, 那么我们前端有没有办法也实现这个逐个播放的功能呢…
事实上, 浏览器并没有给我们提供控制GIF的API(据说曾经好像有, 但因为用户体验的问题被废除了, 关于这点我没有去考证过), 我们无法得知这个动画是否已经结束了, 或者控制它的播放和停止.
网上并没有太多关于这方面的资料, 不过张鑫旭dalao的这篇文章. 其中一个方法给我一个思路 —— 虽然img并没有这种事件, 但是我们可以使用canvas做替换呀.
说干就干, 这里借鉴dalao的代码, 对HTMLImageElement(可以用来操纵<img>元素的布局和图像)的原型作扩展, 增加两个方法:
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | if ('getContext' in document.createElement('canvas')) {
       HTMLImageElement.prototype.play = function () {     if (this.storeCanvas) {              this.storeCanvas.parentElement.removeChild(this.storeCanvas);       this.storeCanvas = null;              this.style.opacity = '';     }     if (this.storeUrl) {       this.src = this.storeUrl;     }   };
       HTMLImageElement.prototype.stop = function () {
      const canvas = document.createElement('canvas');     let width = this.width;     let height = this.height;
      if (width && height) {              if (!this.storeUrl) {         this.storeUrl = this.src;       }
               canvas.width = width;       canvas.height = height;       canvas.getContext('2d').drawImage(this, 0, 0, width, height);
               try {         this.src = canvas.toDataURL("image/gif");       } catch (e) {                  this.removeAttribute('src');         canvas.style.position = 'absolute';
                   this.parentElement.insertBefore(canvas, this);         this.style.opacity = '0';         this.storeCanvas = canvas;       }     }   }; }
  | 
因为前台并没有我们想要操作图片的事件, 因此无法得知gif能持续多少秒, 这点只能由服务端来判断. 所幸这次情况没有那么复杂, 我们不需要适配随机的GIF. 就根据我们手头的动图计算有多少帧, 查看得知每个GIF播放时间都为3s. 再使用定时器的方式去调用方法, 为了防止用户没看清GIF的动作, 因此在定时器时间上再翻了一倍.
页面代码大致如下:
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 35 36 37 38 39 40 41 42 43
   | <div class="body">          <h3 class="body-title">观影指南</h3>     <div class="body-tips">       <div class="tips-line clearfix">         <div class="tips-group">           <div class="tips-img">             <img src="./tips-1.gif" alt="tips-1">             <span class="tips-bar"></span>           </div>           <p>1.坐上座椅,系好安全带</p>         </div>                  <div class="tips-group">           <div class="tips-img">             <img src="./tips-2.gif" alt="tips-2">             <span class="tips-bar"></span>           </div>           <p>2.于右手边取眼镜佩戴,并带上耳机</p>         </div>                </div>              <div class="tips-line clearfix">         <div class="tips-group">           <div class="tips-img">             <img src="./tips-3.gif" alt="tips-3">             <span class="tips-bar"></span>           </div>           <p>3.按下扶手上的按钮,开始观影</p>         </div>         <div class="tips-group">           <div class="tips-img">             <img src="./tips-4.gif" alt="tips-4">             <span class="tips-bar"></span>           </div>           <p>4.如感不适,长按按钮停止观 影</p>         </div>       </div>            </div>        </div>
   | 
首先选择全部目标GIF, 使其暂停(初始化). 紧接着包装一下定时器用函数调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   |  const images = document.querySelectorAll('.tips-img img') let palyTimer = null;
  function palyGif (num = 0) {   clearTimeout(palyTimer)   images[num].play()
       palyTimer = setTimeout(() => {     images[num].stop()     num = (images.length - 1 <= num) ? 0 : ++num     images[num].play()     return palyGif(num)   }, 6000); }
 
  images.forEach(img => img.addEventListener('load', img.stop, { once: true }))
  setTimeout(() => palyGif(), 50);
 
  | 
仅仅几行代码留实现我们想要的效果啦(图片压了下):
![]()
至于微博那种逐个播放的效果, 我原本想在控制台研究一下它实现的原理. 但仔细一看, 发现微博动图在手机客户端和非客户端上的效果是不一样的. 也就是说在安卓客户端上的确逐个播放, 但是在手机网页上却是一起播放, 并没有实现这个功能, PC页面同理, 因此推测并不是使用js实现的.
最后各位看官如果有什么好的想法的话, 可以留个言一起交流一下呗~