Files
fitness-coach/miniprogram/components/gif-player/gif-player.js

113 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// gif-player微信 <image> 组件不播放 GIF 动画(只渲染首帧),
// 这里用 Canvas 2D + 纯 JS 解码器(omggif)逐帧绘制,实现真正的动图播放。
const { GifReader } = require('../../utils/vendor/omggif.js');
Component({
properties: {
// GIF 地址(通常是 ex.gifUrl超分后的 720px 动图)
src: {
type: String,
value: '',
// src 可能在组件 attached 之后才由父页面异步设上,用 observer 兜底重新加载
observer(value) {
if (value) this._load();
}
}
},
data: {
failed: false
},
lifetimes: {
attached() {
this._aborted = false;
this._timer = null;
this._reader = null;
this._ctx = null;
this._rgba = null;
this._frame = 0;
if (this.data.src) this._load();
},
detached() {
this._abort();
}
},
methods: {
_abort() {
this._aborted = true;
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
},
// 下载 GIF 二进制并构造解码器(解耦于画布,便于画布未就绪时重试渲染)
_load() {
const src = this.data.src;
if (!src || this._aborted) return;
// 防重复attached 与 observer 可能都触发;同一 src 只加载一次
if (this._loadedSrc === src && this._reader) return;
this._loadedSrc = src;
this._abort(); // 清掉上一次可能的循环定时器
wx.request({
url: src,
responseType: 'arraybuffer',
success: (res) => {
if (this._aborted) return;
try {
this._reader = new GifReader(new Uint8Array(res.data));
this._start();
} catch (e) {
console.error('[gif-player] decode fail', e);
this.setData({ failed: true });
}
},
fail: () => {
if (!this._aborted) this.setData({ failed: true });
}
});
},
// 等待 Canvas 2D 节点就绪(自定义组件里 attached 时可能还没布局好,最多重试 10 次)
_start(attempt = 0) {
if (this._aborted || !this._reader) return;
const q = wx.createSelectorQuery().in(this);
q.select('#gifCanvas').fields({ node: true, size: true }).exec((rs) => {
if (this._aborted) return;
if (!rs || !rs[0] || !rs[0].node) {
if (attempt < 10) setTimeout(() => this._start(attempt + 1), 60);
return;
}
const canvas = rs[0].node;
const ctx = canvas.getContext('2d');
const w = this._reader.width;
const h = this._reader.height;
// 画布内部分辨率 = GIF 原始分辨率(已超分到 720CSS 负责缩放到容器
canvas.width = w;
canvas.height = h;
this._ctx = ctx;
this._rgba = new Uint8ClampedArray(w * h * 4);
this._frame = 0;
this._render();
});
},
_render() {
if (this._aborted || !this._reader || !this._ctx) return;
const r = this._reader;
const n = r.numFrames();
if (!n) { this.setData({ failed: true }); return; }
try {
// decodeAndBlitFrameRGBA 按 GIF 的 disposal 规则在 rgba 上累积绘制
r.decodeAndBlitFrameRGBA(this._frame, this._rgba);
const img = this._ctx.createImageData(r.width, r.height);
img.data.set(this._rgba);
this._ctx.putImageData(img, 0, 0);
} catch (e) {
console.error('[gif-player] frame fail', e);
this.setData({ failed: true });
return;
}
const info = r.frameInfo(this._frame);
// omggif 的 delay 单位是 1/100 秒,需 ×10 转毫秒;缺失时给 100ms
const delay = (info && info.delay) ? info.delay * 10 : 100;
this._frame = (this._frame + 1) % n;
// 单帧 GIF 只画一次;多帧才循环。最小 40ms 避免极端快帧
if (n > 1) this._timer = setTimeout(() => this._render(), Math.max(40, delay));
}
}
});