0%

关于移动端GIF动图逐个播放的思路

最近碰到了一个需求, 大致是移动端有一个提示页, 在页面中会四个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) {
// 移除存储的canvas
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 和图片相同宽高
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">
<!-- .split-line End -->
<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>
<!-- .tips-group End -->
<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>
<!-- .tips-group End -->
</div>
<!-- .tips-line End -->
<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>
<!-- .tips-line End -->
</div>
<!-- .body-tips End -->
</div>

首先选择全部目标GIF, 使其暂停(初始化). 紧接着包装一下定时器用函数调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 注意这里是 ES6 的写法
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实现的.

最后各位看官如果有什么好的想法的话, 可以留个言一起交流一下呗~

「请笔者喝杯奶茶鼓励一下」

欢迎关注我的其它发布渠道