84 lines
2.7 KiB
JavaScript
84 lines
2.7 KiB
JavaScript
// gif-player:微信 <image> 组件不播放 GIF 动画(只渲染首帧),
|
||
// 这里用 Canvas 2D + 纯 JS 解码器(omggif)逐帧绘制,实现真正的动图播放。
|
||
const { GifReader } = require('../../utils/vendor/omggif.js');
|
||
|
||
Component({
|
||
properties: {
|
||
// GIF 地址(通常是 ex.gifUrl,原始 180px 动图)
|
||
src: { type: String, value: '' }
|
||
},
|
||
data: {
|
||
failed: false
|
||
},
|
||
lifetimes: {
|
||
attached() {
|
||
this._aborted = false;
|
||
this._timer = null;
|
||
if (this.data.src) this._load();
|
||
},
|
||
detached() {
|
||
this._aborted = true;
|
||
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
|
||
}
|
||
},
|
||
methods: {
|
||
_load() {
|
||
const src = this.data.src;
|
||
if (!src) return;
|
||
wx.request({
|
||
url: src,
|
||
responseType: 'arraybuffer',
|
||
success: (res) => {
|
||
if (this._aborted) return;
|
||
try {
|
||
this._play(res.data);
|
||
} catch (e) {
|
||
console.error('[gif-player] decode fail', e);
|
||
this.setData({ failed: true });
|
||
}
|
||
},
|
||
fail: () => {
|
||
if (!this._aborted) this.setData({ failed: true });
|
||
}
|
||
});
|
||
},
|
||
_play(buffer) {
|
||
const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer);
|
||
const reader = new GifReader(bytes);
|
||
const numFrames = reader.numFrames();
|
||
if (!numFrames) { this.setData({ failed: true }); return; }
|
||
const w = reader.width;
|
||
const h = reader.height;
|
||
const q = wx.createSelectorQuery().in(this);
|
||
q.select('#gifCanvas').fields({ node: true, size: true }).exec((rs) => {
|
||
if (this._aborted || !rs || !rs[0] || !rs[0].node) return;
|
||
const canvas = rs[0].node;
|
||
const ctx = canvas.getContext('2d');
|
||
// 画布内部分辨率=GIF 原始分辨率;CSS 负责缩放到容器
|
||
canvas.width = w;
|
||
canvas.height = h;
|
||
const rgba = new Uint8ClampedArray(w * h * 4);
|
||
let frame = 0;
|
||
const render = () => {
|
||
if (this._aborted) return;
|
||
try {
|
||
// decodeAndBlitFrameRGBA 会按 GIF 的 disposal 规则在 rgba 上累积绘制
|
||
reader.decodeAndBlitFrameRGBA(frame, rgba);
|
||
const img = ctx.createImageData(w, h);
|
||
img.data.set(rgba);
|
||
ctx.putImageData(img, 0, 0);
|
||
} catch (e) {
|
||
console.error('[gif-player] frame fail', e);
|
||
return;
|
||
}
|
||
const info = reader.frameInfo(frame);
|
||
const delay = info && info.delay ? info.delay : 100;
|
||
frame = (frame + 1) % numFrames;
|
||
this._timer = setTimeout(render, Math.max(16, delay));
|
||
};
|
||
render();
|
||
});
|
||
}
|
||
}
|
||
});
|