fix: gif-player 修复_abort误杀异步回调导致彻底不播放 + 加载中提示
This commit is contained in:
@@ -4,18 +4,19 @@ const { GifReader } = require('../../utils/vendor/omggif.js');
|
|||||||
|
|
||||||
Component({
|
Component({
|
||||||
properties: {
|
properties: {
|
||||||
// GIF 地址(通常是 ex.gifUrl,超分后的 720px 动图)
|
// GIF 地址(通常是 ex.gifUrl,超分后的清晰动图)。
|
||||||
|
// 组件可能在 attached 之后才拿到 src(父页面接口异步返回),用 observer 兜底重新加载。
|
||||||
src: {
|
src: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
value: '',
|
||||||
// src 可能在组件 attached 之后才由父页面异步设上,用 observer 兜底重新加载
|
|
||||||
observer(value) {
|
observer(value) {
|
||||||
if (value) this._load();
|
if (value) this._load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
failed: false
|
failed: false,
|
||||||
|
loading: true
|
||||||
},
|
},
|
||||||
lifetimes: {
|
lifetimes: {
|
||||||
attached() {
|
attached() {
|
||||||
@@ -25,51 +26,55 @@ Component({
|
|||||||
this._ctx = null;
|
this._ctx = null;
|
||||||
this._rgba = null;
|
this._rgba = null;
|
||||||
this._frame = 0;
|
this._frame = 0;
|
||||||
|
this._loadedSrc = '';
|
||||||
if (this.data.src) this._load();
|
if (this.data.src) this._load();
|
||||||
},
|
},
|
||||||
detached() {
|
detached() {
|
||||||
this._abort();
|
// 仅 detached 时彻底停止;不要把 _aborted 留给异步回调误判
|
||||||
|
this._aborted = true;
|
||||||
|
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
_abort() {
|
// 清除上一次可能的循环定时器(不动 _aborted 标志,否则会误杀正在进行的异步请求)
|
||||||
this._aborted = true;
|
_clearTimer() {
|
||||||
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
|
if (this._timer) { clearTimeout(this._timer); this._timer = null; }
|
||||||
},
|
},
|
||||||
// 下载 GIF 二进制并构造解码器(解耦于画布,便于画布未就绪时重试渲染)
|
|
||||||
_load() {
|
_load() {
|
||||||
const src = this.data.src;
|
const src = this.data.src;
|
||||||
if (!src || this._aborted) return;
|
if (!src || this._aborted) return;
|
||||||
// 防重复:attached 与 observer 可能都触发;同一 src 只加载一次
|
// 同一 src 且已解码完成则跳过,避免 attached 与 observer 重复触发
|
||||||
if (this._loadedSrc === src && this._reader) return;
|
if (this._loadedSrc === src && this._reader) return;
|
||||||
this._loadedSrc = src;
|
this._loadedSrc = src;
|
||||||
this._abort(); // 清掉上一次可能的循环定时器
|
this._clearTimer();
|
||||||
|
this.setData({ loading: true, failed: false });
|
||||||
wx.request({
|
wx.request({
|
||||||
url: src,
|
url: src,
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
// detached 之后才允许放弃
|
||||||
if (this._aborted) return;
|
if (this._aborted) return;
|
||||||
try {
|
try {
|
||||||
this._reader = new GifReader(new Uint8Array(res.data));
|
this._reader = new GifReader(new Uint8Array(res.data));
|
||||||
this._start();
|
this._start();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[gif-player] decode fail', e);
|
console.error('[gif-player] decode fail', e);
|
||||||
this.setData({ failed: true });
|
this.setData({ failed: true, loading: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: () => {
|
||||||
if (!this._aborted) this.setData({ failed: true });
|
if (!this._aborted) this.setData({ failed: true, loading: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 等待 Canvas 2D 节点就绪(自定义组件里 attached 时可能还没布局好,最多重试 10 次)
|
// 等待 Canvas 2D 节点就绪(自定义组件 attached 时可能还没布局好,最多重试 15 次)
|
||||||
_start(attempt = 0) {
|
_start(attempt = 0) {
|
||||||
if (this._aborted || !this._reader) return;
|
if (this._aborted || !this._reader) return;
|
||||||
const q = wx.createSelectorQuery().in(this);
|
const q = wx.createSelectorQuery().in(this);
|
||||||
q.select('#gifCanvas').fields({ node: true, size: true }).exec((rs) => {
|
q.select('#gifCanvas').fields({ node: true, size: true }).exec((rs) => {
|
||||||
if (this._aborted) return;
|
if (this._aborted) return;
|
||||||
if (!rs || !rs[0] || !rs[0].node) {
|
if (!rs || !rs[0] || !rs[0].node) {
|
||||||
if (attempt < 10) setTimeout(() => this._start(attempt + 1), 60);
|
if (attempt < 15) setTimeout(() => this._start(attempt + 1), 60);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const canvas = rs[0].node;
|
const canvas = rs[0].node;
|
||||||
@@ -89,7 +94,7 @@ Component({
|
|||||||
if (this._aborted || !this._reader || !this._ctx) return;
|
if (this._aborted || !this._reader || !this._ctx) return;
|
||||||
const r = this._reader;
|
const r = this._reader;
|
||||||
const n = r.numFrames();
|
const n = r.numFrames();
|
||||||
if (!n) { this.setData({ failed: true }); return; }
|
if (!n) { this.setData({ failed: true, loading: false }); return; }
|
||||||
try {
|
try {
|
||||||
// decodeAndBlitFrameRGBA 按 GIF 的 disposal 规则在 rgba 上累积绘制
|
// decodeAndBlitFrameRGBA 按 GIF 的 disposal 规则在 rgba 上累积绘制
|
||||||
r.decodeAndBlitFrameRGBA(this._frame, this._rgba);
|
r.decodeAndBlitFrameRGBA(this._frame, this._rgba);
|
||||||
@@ -98,9 +103,11 @@ Component({
|
|||||||
this._ctx.putImageData(img, 0, 0);
|
this._ctx.putImageData(img, 0, 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[gif-player] frame fail', e);
|
console.error('[gif-player] frame fail', e);
|
||||||
this.setData({ failed: true });
|
this.setData({ failed: true, loading: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 首帧渲染完成,关闭加载提示
|
||||||
|
if (this.data.loading) this.setData({ loading: false });
|
||||||
const info = r.frameInfo(this._frame);
|
const info = r.frameInfo(this._frame);
|
||||||
// omggif 的 delay 单位是 1/100 秒,需 ×10 转毫秒;缺失时给 100ms
|
// omggif 的 delay 单位是 1/100 秒,需 ×10 转毫秒;缺失时给 100ms
|
||||||
const delay = (info && info.delay) ? info.delay * 10 : 100;
|
const delay = (info && info.delay) ? info.delay * 10 : 100;
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
<view class="gif-player">
|
<view class="gif-player">
|
||||||
<canvas wx:if="{{!failed}}" type="2d" id="gifCanvas" class="canvas"></canvas>
|
<canvas wx:if="{{!failed}}" type="2d" id="gifCanvas" class="canvas"></canvas>
|
||||||
|
<!-- 加载中提示:GIF 下载/解码期间画布透明,海报透出,此提示告知用户动图正在准备 -->
|
||||||
|
<view wx:if="{{loading && !failed}}" class="gif-loading">
|
||||||
|
<view class="gif-loading__dot"></view>
|
||||||
|
<text class="gif-loading__txt">动图加载中…</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,2 +1,22 @@
|
|||||||
.gif-player { width: 100%; height: 100%; }
|
.gif-player { width: 100%; height: 100%; position: relative; }
|
||||||
.canvas { width: 100%; height: 100%; display: block; }
|
.canvas { width: 100%; height: 100%; display: block; }
|
||||||
|
|
||||||
|
/* 加载中提示:居中、半透明,避免用户误以为动图没播 */
|
||||||
|
.gif-loading {
|
||||||
|
position: absolute; left: 0; right: 0; bottom: 24rpx;
|
||||||
|
display: flex; align-items: center; justify-content: center; gap: 12rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.gif-loading__dot {
|
||||||
|
width: 18rpx; height: 18rpx; border-radius: 999rpx;
|
||||||
|
background: var(--gold, #c9a24b);
|
||||||
|
animation: gif-pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.gif-loading__txt {
|
||||||
|
font-size: 22rpx; color: rgba(255,255,255,0.82);
|
||||||
|
text-shadow: 0 1rpx 4rpx rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
@keyframes gif-pulse {
|
||||||
|
0%, 100% { opacity: 0.35; transform: scale(0.8); }
|
||||||
|
50% { opacity: 1; transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user