// gif-player:微信 组件不播放 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(); }); } } });