@libshub/gif-tools 1.0.3 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -0
- package/dist/components/GifLoadStatsDebug.d.ts +7 -0
- package/dist/components/GifPlayer.d.ts +2 -2
- package/dist/gif-tools.css +1 -1
- package/dist/gif-tools.es.js +341 -88
- package/dist/index.d.ts +2 -2
- package/dist/utils/gifController.d.ts +5 -2
- package/dist/utils/gifResourceManager.d.ts +3 -3
- package/dist/utils/index.d.ts +4 -1
- package/dist/utils/loadStats.d.ts +18 -0
- package/dist/utils/types.d.ts +16 -3
- package/package.json +4 -2
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @libshub/gif-tools
|
|
2
|
+
|
|
3
|
+
基于 Canvas 的 React GIF 播放组件,使用 [gifuct-js](https://github.com/matt-way/gifuct-js) 解码,支持播放控制、循环次数、资源缓存与加载性能统计。
|
|
4
|
+
|
|
5
|
+
## 演示
|
|
6
|
+
|
|
7
|
+
[](http://www.hanxiaoxin.cn/gif-tools/)
|
|
8
|
+
|
|
9
|
+
在线体验:[http://www.hanxiaoxin.cn/gif-tools/](http://www.hanxiaoxin.cn/gif-tools/)
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @libshub/gif-tools
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
需要 React 17+:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install react react-dom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 快速开始
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { GifPlayer } from '@libshub/gif-tools'
|
|
27
|
+
import '@libshub/gif-tools/style.css'
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
return (
|
|
31
|
+
<GifPlayer
|
|
32
|
+
src="https://example.com/demo.gif"
|
|
33
|
+
autoPlay
|
|
34
|
+
showControls
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## GifPlayer
|
|
41
|
+
|
|
42
|
+
### Props
|
|
43
|
+
|
|
44
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
45
|
+
|------|------|--------|------|
|
|
46
|
+
| `src` | `string` | — | GIF 地址(必填) |
|
|
47
|
+
| `width` | `number \| string` | — | 画布宽度 |
|
|
48
|
+
| `height` | `number \| string` | — | 画布高度 |
|
|
49
|
+
| `className` | `string` | — | 根节点 class |
|
|
50
|
+
| `style` | `CSSProperties` | — | 根节点样式 |
|
|
51
|
+
| `autoPlay` | `boolean` | `true` | 加载完成后自动播放 |
|
|
52
|
+
| `showControls` | `boolean` | `false` | 显示播放/暂停按钮 |
|
|
53
|
+
| `debug` | `boolean` | `false` | 在画面上叠加加载耗时调试信息 |
|
|
54
|
+
| `loopCount` | `number` | — | 循环次数,不传则按 GIF 自身循环或无限循环 |
|
|
55
|
+
| `onLoaded` | `(stats: GifLoadStats) => void` | — | 加载完成 |
|
|
56
|
+
| `onPlay` | `() => void` | — | 开始播放 |
|
|
57
|
+
| `onPause` | `() => void` | — | 暂停 |
|
|
58
|
+
| `onEnd` | `() => void` | — | 达到循环上限后结束 |
|
|
59
|
+
| `onError` | `(error: Error) => void` | — | 加载或解码失败 |
|
|
60
|
+
|
|
61
|
+
### Ref 方法
|
|
62
|
+
|
|
63
|
+
通过 `ref` 可命令式控制播放器:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { useRef } from 'react'
|
|
67
|
+
import { GifPlayer, type GifPlayerRef } from '@libshub/gif-tools'
|
|
68
|
+
|
|
69
|
+
const ref = useRef<GifPlayerRef>(null)
|
|
70
|
+
|
|
71
|
+
ref.current?.play() // 播放
|
|
72
|
+
ref.current?.pause() // 暂停
|
|
73
|
+
ref.current?.toggle() // 切换播放/暂停
|
|
74
|
+
ref.current?.reset() // 回到第一帧并暂停
|
|
75
|
+
ref.current?.reload() // 重新加载(跳过 pending 复用,强制 fresh)
|
|
76
|
+
ref.current?.isPlaying() // 是否正在播放
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 完整示例
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useRef } from 'react'
|
|
83
|
+
import {
|
|
84
|
+
GifPlayer,
|
|
85
|
+
formatGifLoadStats,
|
|
86
|
+
type GifPlayerRef,
|
|
87
|
+
} from '@libshub/gif-tools'
|
|
88
|
+
import '@libshub/gif-tools/style.css'
|
|
89
|
+
|
|
90
|
+
export default function Demo() {
|
|
91
|
+
const playerRef = useRef<GifPlayerRef>(null)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<button onClick={() => playerRef.current?.toggle()}>播放/暂停</button>
|
|
96
|
+
|
|
97
|
+
<GifPlayer
|
|
98
|
+
ref={playerRef}
|
|
99
|
+
src="https://example.com/demo.gif"
|
|
100
|
+
width="100%"
|
|
101
|
+
height="auto"
|
|
102
|
+
className="my-gif"
|
|
103
|
+
style={{ borderRadius: 8 }}
|
|
104
|
+
autoPlay
|
|
105
|
+
showControls
|
|
106
|
+
loopCount={2}
|
|
107
|
+
debug
|
|
108
|
+
onLoaded={(stats) => console.log(formatGifLoadStats(stats))}
|
|
109
|
+
onPlay={() => console.log('play')}
|
|
110
|
+
onPause={() => console.log('pause')}
|
|
111
|
+
onEnd={() => console.log('end')}
|
|
112
|
+
onError={(e) => console.error(e)}
|
|
113
|
+
/>
|
|
114
|
+
</>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 资源缓存
|
|
120
|
+
|
|
121
|
+
相同 `src` 的 GIF 在全局共享解码结果,多个 `GifPlayer` 实例不会重复 fetch / decode。
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { clearGifResourceCache } from '@libshub/gif-tools'
|
|
125
|
+
|
|
126
|
+
// 清空全部缓存
|
|
127
|
+
clearGifResourceCache()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`onLoaded` 回调中的 `GifLoadStats` 可区分三种加载模式:
|
|
131
|
+
|
|
132
|
+
- **fresh**:首次加载,包含 `fetchTimeMs` 与 `decodeTimeMs`
|
|
133
|
+
- **pending**:复用进行中的请求,包含 `pendingWaitFetchMs` 与 `pendingWaitDecodeMs`
|
|
134
|
+
- **cache**:命中缓存,耗时均为 0
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { formatGifLoadStats, getGifLoadStatsView } from '@libshub/gif-tools'
|
|
138
|
+
|
|
139
|
+
onLoaded={(stats) => {
|
|
140
|
+
console.log(formatGifLoadStats(stats))
|
|
141
|
+
// fresh
|
|
142
|
+
// fetch 120.5ms
|
|
143
|
+
// decode 45.2ms
|
|
144
|
+
// total 165.7ms
|
|
145
|
+
|
|
146
|
+
const view = getGifLoadStatsView(stats)
|
|
147
|
+
console.log(view.mode) // 'fresh' | 'pending' | 'cache'
|
|
148
|
+
}}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 底层 API
|
|
152
|
+
|
|
153
|
+
不依赖 React 时,可直接在 Canvas 上创建控制器:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { createGifController } from '@libshub/gif-tools'
|
|
157
|
+
|
|
158
|
+
const canvas = document.querySelector('canvas')!
|
|
159
|
+
const { controller, stats } = await createGifController(canvas, src, {
|
|
160
|
+
loopCount: 3,
|
|
161
|
+
onPlay: () => {},
|
|
162
|
+
onPause: () => {},
|
|
163
|
+
onEnd: () => {},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
controller.play()
|
|
167
|
+
controller.pause()
|
|
168
|
+
controller.reset()
|
|
169
|
+
controller.isPlaying()
|
|
170
|
+
controller.getCompletedLoops()
|
|
171
|
+
controller.destroy()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 导出一览
|
|
175
|
+
|
|
176
|
+
**组件**
|
|
177
|
+
|
|
178
|
+
- `GifPlayer`
|
|
179
|
+
|
|
180
|
+
**类型**
|
|
181
|
+
|
|
182
|
+
- `GifPlayerProps`、`GifPlayerRef`、`GifPlayerComponent`
|
|
183
|
+
- `GifLoadStats`、`GifController`、`CreateGifOptions`
|
|
184
|
+
- `GifLoadStatsView`、`GifLoadStatsLine`、`GifLoadStatsMode`
|
|
185
|
+
|
|
186
|
+
**工具函数**
|
|
187
|
+
|
|
188
|
+
- `createGifController` — 在 Canvas 上创建 GIF 控制器
|
|
189
|
+
- `clearGifResourceCache` — 清空 GIF 资源缓存
|
|
190
|
+
- `formatGifLoadStats` — 格式化加载统计(多行)
|
|
191
|
+
- `formatLoadTimeMs` — 格式化毫秒数
|
|
192
|
+
- `getGifLoadStatsView` — 获取结构化加载统计视图
|
|
193
|
+
- `getTotalLoadTimeMs` — 获取总加载耗时
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { GifLoadStats } from '../utils';
|
|
3
|
+
export declare function GifLoadStatsDebug({ stats, canvasRef, visible, }: {
|
|
4
|
+
stats: GifLoadStats;
|
|
5
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
6
|
+
visible: boolean;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CSSProperties, type ForwardRefExoticComponent, type RefAttributes } from 'react';
|
|
2
|
+
import type { GifLoadStats } from '../utils';
|
|
2
3
|
import './GifPlayer.css';
|
|
3
4
|
export interface GifPlayerRef {
|
|
4
5
|
play: () => void;
|
|
@@ -17,12 +18,11 @@ export interface GifPlayerProps {
|
|
|
17
18
|
autoPlay?: boolean;
|
|
18
19
|
showControls?: boolean;
|
|
19
20
|
debug?: boolean;
|
|
20
|
-
/** 未设置则无限循环;1 播放一次;N 播放 N 次后触发 onEnd */
|
|
21
21
|
loopCount?: number;
|
|
22
22
|
onPlay?: () => void;
|
|
23
23
|
onPause?: () => void;
|
|
24
24
|
onEnd?: () => void;
|
|
25
|
-
onLoaded?: (
|
|
25
|
+
onLoaded?: (stats: GifLoadStats) => void;
|
|
26
26
|
onError?: (error: Error) => void;
|
|
27
27
|
}
|
|
28
28
|
export type GifPlayerComponent = ForwardRefExoticComponent<GifPlayerProps & RefAttributes<GifPlayerRef>>;
|
package/dist/gif-tools.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.gif-player{line-height:0;display:inline-block;position:relative;overflow:
|
|
1
|
+
.gif-player{line-height:0;display:inline-block;position:relative;overflow:visible}.gif-player__media{max-width:100%;height:auto;display:block}.gif-player__controls{opacity:0;gap:6px;transition:opacity .2s;display:flex;position:absolute;bottom:8px;right:8px}.gif-player:hover .gif-player__controls,.gif-player--show-controls .gif-player__controls{opacity:1}.gif-player__btn{color:#fff;cursor:pointer;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0000008c;border:none;border-radius:50%;justify-content:center;align-items:center;width:32px;height:32px;padding:0;display:flex}.gif-player__btn:hover{background:#000000bf}.gif-player__btn svg{fill:currentColor;width:16px;height:16px}.gif-player__debug{z-index:1;pointer-events:none;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);font-variant-numeric:tabular-nums;color:#e8eaed;background:#0c0e12d1;font-family:ui-monospace,Cascadia Code,SF Mono,monospace;position:absolute;top:0;left:0;box-shadow:0 2px 8px #0003}.gif-player__debug--compact{white-space:nowrap;border-radius:0 0 4px;padding:2px 5px;font-size:8px;line-height:1.2}.gif-player__debug--medium{border-radius:0 0 5px;padding:3px 6px;font-size:9px;line-height:1.2}.gif-player__debug--collapsible{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none}.gif-player__debug--collapsible:hover{background:#12161ceb}.gif-player__debug--collapsible:focus-visible{outline-offset:1px;outline:1px solid #7ec8ffbf}.gif-player__debug--full{border-radius:0 0 6px;min-width:96px;padding:5px 7px;font-size:10px;line-height:1.35}.gif-player__debug--expanded{z-index:2;min-width:108px;max-width:none!important}.gif-player__debug-head{justify-content:space-between;align-items:center;gap:8px;display:flex}.gif-player__debug-badge{letter-spacing:.04em;color:#fff;background:#ffffff24;border-radius:3px;padding:1px 4px;font-size:8px;font-weight:600;display:inline-block}.gif-player__debug--full .gif-player__debug-badge{margin-bottom:4px;padding:1px 5px;font-size:9px}.gif-player__debug[data-mode=fresh] .gif-player__debug-badge{background:#4a90e28c}.gif-player__debug[data-mode=pending] .gif-player__debug-badge{background:#e6a23c8c}.gif-player__debug[data-mode=cache] .gif-player__debug-badge{background:#52c4808c}.gif-player__debug-body{flex-direction:column;gap:2px;display:flex}.gif-player__debug-row,.gif-player__debug-total{justify-content:space-between;align-items:baseline;gap:8px;display:flex}.gif-player__debug-label{color:#e8eaedb8;white-space:nowrap}.gif-player__debug-value{color:#fff;white-space:nowrap}.gif-player__debug-value--total{color:#7ec8ff;font-weight:600}.gif-player__debug[data-mode=cache] .gif-player__debug-value--total{color:#82e0aa}.gif-player__debug-total{border-top:1px solid #ffffff1f;margin-top:4px;padding-top:4px}.gif-player__debug-total .gif-player__debug-label{color:#e8eaede6;font-weight:600}.gif-player__debug-total .gif-player__debug-value{color:#7ec8ff;font-weight:600}.gif-player__debug[data-mode=cache] .gif-player__debug-total .gif-player__debug-value{color:#82e0aa}
|
|
2
2
|
/*$vite$:1*/
|
package/dist/gif-tools.es.js
CHANGED
|
@@ -299,64 +299,122 @@ var c = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
|
|
|
299
299
|
});
|
|
300
300
|
};
|
|
301
301
|
})))(), h = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Map();
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
function v(e, t, n) {
|
|
303
|
+
return n === null || e >= n ? {
|
|
304
|
+
pendingWaitFetchMs: 0,
|
|
305
|
+
pendingWaitDecodeMs: t - e
|
|
306
|
+
} : {
|
|
307
|
+
pendingWaitFetchMs: n - e,
|
|
308
|
+
pendingWaitDecodeMs: t - n
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function y() {
|
|
312
|
+
return {
|
|
313
|
+
fetchTimeMs: 0,
|
|
314
|
+
decodeTimeMs: 0,
|
|
315
|
+
pendingWaitFetchMs: 0,
|
|
316
|
+
pendingWaitDecodeMs: 0,
|
|
317
|
+
fromCache: !0,
|
|
318
|
+
fromPending: !1
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function b(e, t) {
|
|
322
|
+
return {
|
|
323
|
+
fetchTimeMs: e,
|
|
324
|
+
decodeTimeMs: t,
|
|
325
|
+
pendingWaitFetchMs: 0,
|
|
326
|
+
pendingWaitDecodeMs: 0,
|
|
327
|
+
fromCache: !1,
|
|
328
|
+
fromPending: !1
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async function x(e, t) {
|
|
332
|
+
let n = performance.now(), r = await fetch(e, {
|
|
304
333
|
mode: "cors",
|
|
305
334
|
credentials: "omit"
|
|
306
335
|
});
|
|
307
|
-
if (!
|
|
308
|
-
let
|
|
309
|
-
|
|
336
|
+
if (!r.ok) throw Error(`Failed to load gif: ${r.status}`);
|
|
337
|
+
let i = await r.arrayBuffer(), a = performance.now() - n;
|
|
338
|
+
t && (t.fetchDoneAt = performance.now());
|
|
339
|
+
let o = performance.now(), s = (0, m.parseGIF)(i), c = (0, m.decompressFrames)(s, !0), l = performance.now() - o;
|
|
340
|
+
if (!c.length) throw Error("GIF has no frames");
|
|
310
341
|
return {
|
|
311
342
|
gif: {
|
|
312
|
-
frames:
|
|
313
|
-
width:
|
|
314
|
-
height:
|
|
343
|
+
frames: c,
|
|
344
|
+
width: s.lsd.width,
|
|
345
|
+
height: s.lsd.height
|
|
315
346
|
},
|
|
316
|
-
|
|
347
|
+
fetchTimeMs: a,
|
|
348
|
+
decodeTimeMs: l
|
|
317
349
|
};
|
|
318
350
|
}
|
|
319
|
-
async function
|
|
351
|
+
async function S(e, t) {
|
|
320
352
|
let n = h.get(e);
|
|
321
|
-
if (n) return
|
|
353
|
+
if (n) return {
|
|
322
354
|
gif: n.data,
|
|
323
|
-
|
|
355
|
+
stats: y()
|
|
324
356
|
};
|
|
325
357
|
if (!t?.skipPending && g.has(e)) {
|
|
326
|
-
let { gif:
|
|
327
|
-
return h.
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
let t = g.get(e), n = performance.now(), { gif: r, fetchTimeMs: i, decodeTimeMs: a } = await t.promise, { pendingWaitFetchMs: o, pendingWaitDecodeMs: s } = v(n, performance.now(), t.fetchDoneAt), c = h.get(e);
|
|
359
|
+
return c ||= (h.set(e, {
|
|
360
|
+
data: r,
|
|
361
|
+
refCount: 0,
|
|
362
|
+
fetchTimeMs: i,
|
|
363
|
+
decodeTimeMs: a
|
|
364
|
+
}), h.get(e)), {
|
|
365
|
+
gif: c.data,
|
|
366
|
+
stats: {
|
|
367
|
+
fetchTimeMs: 0,
|
|
368
|
+
decodeTimeMs: 0,
|
|
369
|
+
pendingWaitFetchMs: o,
|
|
370
|
+
pendingWaitDecodeMs: s,
|
|
371
|
+
fromCache: !1,
|
|
372
|
+
fromPending: !0
|
|
373
|
+
}
|
|
330
374
|
};
|
|
331
375
|
}
|
|
332
376
|
let r = (_.get(e) ?? 0) + 1;
|
|
333
377
|
_.set(e, r);
|
|
334
|
-
let i =
|
|
378
|
+
let i = {
|
|
379
|
+
fetchDoneAt: null,
|
|
380
|
+
promise: void 0
|
|
381
|
+
};
|
|
382
|
+
i.promise = x(e, i).then(({ gif: t, fetchTimeMs: n, decodeTimeMs: i }) => _.get(e) === r ? (g.delete(e), h.set(e, {
|
|
335
383
|
data: t,
|
|
336
|
-
refCount: 0
|
|
384
|
+
refCount: 0,
|
|
385
|
+
fetchTimeMs: n,
|
|
386
|
+
decodeTimeMs: i
|
|
337
387
|
}), {
|
|
338
388
|
gif: t,
|
|
339
|
-
|
|
389
|
+
fetchTimeMs: n,
|
|
390
|
+
decodeTimeMs: i
|
|
340
391
|
}) : {
|
|
341
392
|
gif: t,
|
|
342
|
-
|
|
393
|
+
fetchTimeMs: n,
|
|
394
|
+
decodeTimeMs: i
|
|
343
395
|
}).catch((t) => {
|
|
344
396
|
throw _.get(e) === r && g.delete(e), t;
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return _.get(e) === r && (h.get(e).refCount += 1), {
|
|
397
|
+
}), g.set(e, i);
|
|
398
|
+
let { gif: a, fetchTimeMs: o, decodeTimeMs: s } = await i.promise;
|
|
399
|
+
return {
|
|
349
400
|
gif: a,
|
|
350
|
-
|
|
401
|
+
stats: b(o, s)
|
|
351
402
|
};
|
|
352
403
|
}
|
|
353
|
-
function
|
|
404
|
+
function C(e) {
|
|
405
|
+
let t = h.get(e);
|
|
406
|
+
t && (t.refCount += 1);
|
|
407
|
+
}
|
|
408
|
+
function w(e) {
|
|
354
409
|
let t = h.get(e);
|
|
355
410
|
t && (--t.refCount, t.refCount <= 0 && h.delete(e));
|
|
356
411
|
}
|
|
412
|
+
function T() {
|
|
413
|
+
h.clear(), g.clear(), _.clear();
|
|
414
|
+
}
|
|
357
415
|
//#endregion
|
|
358
416
|
//#region src/utils/gifController.ts
|
|
359
|
-
function
|
|
417
|
+
function E(e, t, n, r, i = {}) {
|
|
360
418
|
let a = e.getContext("2d");
|
|
361
419
|
if (!a) throw Error("Canvas 2d context unavailable");
|
|
362
420
|
let o = a;
|
|
@@ -400,8 +458,8 @@ function x(e, t, n, r, i = {}) {
|
|
|
400
458
|
function x() {
|
|
401
459
|
b(), u = 0, d = 0, o.clearRect(0, 0, n, r), g(0);
|
|
402
460
|
}
|
|
403
|
-
function S() {
|
|
404
|
-
b(), p = null, s.width = 0, s.height = 0, e.width = 0, e.height = 0;
|
|
461
|
+
function S(t) {
|
|
462
|
+
b(), p = null, s.width = 0, s.height = 0, t?.clearCanvas !== !1 && (e.width = 0, e.height = 0);
|
|
405
463
|
}
|
|
406
464
|
return g(0), {
|
|
407
465
|
play: y,
|
|
@@ -412,58 +470,252 @@ function x(e, t, n, r, i = {}) {
|
|
|
412
470
|
getCompletedLoops: () => d
|
|
413
471
|
};
|
|
414
472
|
}
|
|
415
|
-
async function
|
|
416
|
-
let { skipPending: r, onLoaded: i, ...a } = n, { gif: o,
|
|
417
|
-
i?.(s)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
473
|
+
async function D(e, t, n = {}) {
|
|
474
|
+
let { skipPending: r, onLoaded: i, ...a } = n, { gif: o, stats: s } = await S(t, { skipPending: r });
|
|
475
|
+
return i?.(s), {
|
|
476
|
+
controller: E(e, o.frames, o.width, o.height, a),
|
|
477
|
+
stats: s
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/utils/loadStats.ts
|
|
482
|
+
function O(e) {
|
|
483
|
+
return e.fromCache ? 0 : e.fromPending ? e.pendingWaitFetchMs + e.pendingWaitDecodeMs : e.fetchTimeMs + e.decodeTimeMs;
|
|
484
|
+
}
|
|
485
|
+
function k(e) {
|
|
486
|
+
return e.fromCache ? {
|
|
487
|
+
mode: "cache",
|
|
488
|
+
lines: [{
|
|
489
|
+
label: "wait fetch",
|
|
490
|
+
valueMs: 0
|
|
491
|
+
}, {
|
|
492
|
+
label: "wait decode",
|
|
493
|
+
valueMs: 0
|
|
494
|
+
}],
|
|
495
|
+
totalMs: 0
|
|
496
|
+
} : e.fromPending ? {
|
|
497
|
+
mode: "pending",
|
|
498
|
+
lines: [{
|
|
499
|
+
label: "wait fetch",
|
|
500
|
+
valueMs: e.pendingWaitFetchMs
|
|
501
|
+
}, {
|
|
502
|
+
label: "wait decode",
|
|
503
|
+
valueMs: e.pendingWaitDecodeMs
|
|
504
|
+
}],
|
|
505
|
+
totalMs: e.pendingWaitFetchMs + e.pendingWaitDecodeMs
|
|
506
|
+
} : {
|
|
507
|
+
mode: "fresh",
|
|
508
|
+
lines: [{
|
|
509
|
+
label: "fetch",
|
|
510
|
+
valueMs: e.fetchTimeMs
|
|
511
|
+
}, {
|
|
512
|
+
label: "decode",
|
|
513
|
+
valueMs: e.decodeTimeMs
|
|
514
|
+
}],
|
|
515
|
+
totalMs: e.fetchTimeMs + e.decodeTimeMs
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function A(e) {
|
|
519
|
+
let t = k(e), n = t.lines.map((e) => `${e.label} ${e.valueMs.toFixed(1)}ms`).join("\n");
|
|
520
|
+
return `${t.mode}\n${n}\ntotal ${t.totalMs.toFixed(1)}ms`;
|
|
521
|
+
}
|
|
522
|
+
function j(e) {
|
|
523
|
+
return `${e.toFixed(1)}ms`;
|
|
524
|
+
}
|
|
525
|
+
function M(e, t) {
|
|
526
|
+
let n = Math.min(e, t);
|
|
527
|
+
return n < 130 ? "compact" : n < 220 ? "medium" : "full";
|
|
528
|
+
}
|
|
529
|
+
var N = {
|
|
530
|
+
fresh: "F",
|
|
531
|
+
pending: "P",
|
|
532
|
+
cache: "C"
|
|
533
|
+
};
|
|
534
|
+
function P(e) {
|
|
535
|
+
let t = k(e), n = N[t.mode];
|
|
536
|
+
return t.mode === "cache" ? `${n} · 0` : `${n} · ${j(t.totalMs)}`;
|
|
537
|
+
}
|
|
538
|
+
function F(e, t) {
|
|
539
|
+
return t === "fresh" ? e.label === "fetch" ? "f" : "d" : e.label === "wait fetch" ? "wf" : "wd";
|
|
540
|
+
}
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/components/GifLoadStatsDebug.tsx
|
|
543
|
+
var I = {
|
|
544
|
+
fresh: "FRESH",
|
|
545
|
+
pending: "PND",
|
|
546
|
+
cache: "CACHE"
|
|
547
|
+
};
|
|
548
|
+
function L(e, t) {
|
|
549
|
+
let [r, i] = a({
|
|
550
|
+
width: 0,
|
|
551
|
+
height: 0
|
|
552
|
+
});
|
|
553
|
+
return n(() => {
|
|
554
|
+
let n = e.current;
|
|
555
|
+
if (!n || !t) return;
|
|
556
|
+
let r = () => {
|
|
557
|
+
i({
|
|
558
|
+
width: n.clientWidth,
|
|
559
|
+
height: n.clientHeight
|
|
560
|
+
});
|
|
561
|
+
};
|
|
562
|
+
r();
|
|
563
|
+
let a = new ResizeObserver(r);
|
|
564
|
+
return a.observe(n), () => a.disconnect();
|
|
565
|
+
}, [e, t]), r;
|
|
566
|
+
}
|
|
567
|
+
function R({ label: e, valueMs: t }) {
|
|
568
|
+
return /* @__PURE__ */ s("div", {
|
|
569
|
+
className: "gif-player__debug-row",
|
|
570
|
+
children: [/* @__PURE__ */ o("span", {
|
|
571
|
+
className: "gif-player__debug-label",
|
|
572
|
+
children: e
|
|
573
|
+
}), /* @__PURE__ */ o("span", {
|
|
574
|
+
className: "gif-player__debug-value",
|
|
575
|
+
children: j(t)
|
|
576
|
+
})]
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function z({ view: e, width: t, expanded: n, onToggle: r }) {
|
|
580
|
+
return /* @__PURE__ */ s("div", {
|
|
581
|
+
className: [
|
|
582
|
+
"gif-player__debug",
|
|
583
|
+
"gif-player__debug--full",
|
|
584
|
+
n && "gif-player__debug--expanded",
|
|
585
|
+
r && "gif-player__debug--collapsible"
|
|
586
|
+
].filter(Boolean).join(" "),
|
|
587
|
+
"data-mode": e.mode,
|
|
588
|
+
style: { maxWidth: !n && t > 0 ? Math.min(152, Math.round(t * .52)) : void 0 },
|
|
589
|
+
role: r ? "button" : void 0,
|
|
590
|
+
tabIndex: r ? 0 : void 0,
|
|
591
|
+
title: r ? n ? "点击收起" : "点击展开详情" : void 0,
|
|
592
|
+
onClick: r,
|
|
593
|
+
onKeyDown: r ? (e) => {
|
|
594
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), r());
|
|
595
|
+
} : void 0,
|
|
596
|
+
children: [
|
|
597
|
+
/* @__PURE__ */ o("span", {
|
|
598
|
+
className: "gif-player__debug-badge",
|
|
599
|
+
children: I[e.mode]
|
|
600
|
+
}),
|
|
601
|
+
/* @__PURE__ */ o("div", {
|
|
602
|
+
className: "gif-player__debug-body",
|
|
603
|
+
children: e.lines.map((t) => /* @__PURE__ */ o(R, {
|
|
604
|
+
label: F(t, e.mode),
|
|
605
|
+
valueMs: t.valueMs
|
|
606
|
+
}, t.label))
|
|
607
|
+
}),
|
|
608
|
+
/* @__PURE__ */ s("div", {
|
|
609
|
+
className: "gif-player__debug-total",
|
|
610
|
+
children: [/* @__PURE__ */ o("span", {
|
|
611
|
+
className: "gif-player__debug-label",
|
|
612
|
+
children: "Σ"
|
|
613
|
+
}), /* @__PURE__ */ o("span", {
|
|
614
|
+
className: "gif-player__debug-value",
|
|
615
|
+
children: j(e.totalMs)
|
|
616
|
+
})]
|
|
617
|
+
})
|
|
618
|
+
]
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function B({ stats: e, canvasRef: t, visible: r }) {
|
|
622
|
+
let { width: i, height: c } = L(t, r), l = k(e), u = M(i, c), [d, f] = a(!1), p = u !== "full";
|
|
623
|
+
n(() => {
|
|
624
|
+
f(!1);
|
|
625
|
+
}, [e]), n(() => {
|
|
626
|
+
p || f(!1);
|
|
627
|
+
}, [p]);
|
|
628
|
+
let m = () => {
|
|
629
|
+
p && f((e) => !e);
|
|
630
|
+
};
|
|
631
|
+
return p && d ? /* @__PURE__ */ o(z, {
|
|
632
|
+
view: l,
|
|
633
|
+
width: i,
|
|
634
|
+
expanded: !0,
|
|
635
|
+
onToggle: m
|
|
636
|
+
}) : u === "compact" ? /* @__PURE__ */ o("div", {
|
|
637
|
+
className: "gif-player__debug gif-player__debug--compact gif-player__debug--collapsible",
|
|
638
|
+
"data-mode": l.mode,
|
|
639
|
+
role: "button",
|
|
640
|
+
tabIndex: 0,
|
|
641
|
+
title: "点击展开详情",
|
|
642
|
+
onClick: m,
|
|
643
|
+
onKeyDown: (e) => {
|
|
644
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), m());
|
|
645
|
+
},
|
|
646
|
+
children: P(e)
|
|
647
|
+
}) : u === "medium" ? /* @__PURE__ */ o("div", {
|
|
648
|
+
className: "gif-player__debug gif-player__debug--medium gif-player__debug--collapsible",
|
|
649
|
+
"data-mode": l.mode,
|
|
650
|
+
role: "button",
|
|
651
|
+
tabIndex: 0,
|
|
652
|
+
title: "点击展开详情",
|
|
653
|
+
onClick: m,
|
|
654
|
+
onKeyDown: (e) => {
|
|
655
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), m());
|
|
656
|
+
},
|
|
657
|
+
children: /* @__PURE__ */ s("div", {
|
|
658
|
+
className: "gif-player__debug-head",
|
|
659
|
+
children: [/* @__PURE__ */ o("span", {
|
|
660
|
+
className: "gif-player__debug-badge",
|
|
661
|
+
children: I[l.mode]
|
|
662
|
+
}), /* @__PURE__ */ o("span", {
|
|
663
|
+
className: "gif-player__debug-value gif-player__debug-value--total",
|
|
664
|
+
children: j(l.totalMs)
|
|
665
|
+
})]
|
|
666
|
+
})
|
|
667
|
+
}) : /* @__PURE__ */ o(z, {
|
|
668
|
+
view: l,
|
|
669
|
+
width: i,
|
|
670
|
+
expanded: !1
|
|
671
|
+
});
|
|
422
672
|
}
|
|
423
673
|
//#endregion
|
|
424
674
|
//#region src/components/GifPlayer.tsx
|
|
425
|
-
var
|
|
426
|
-
let
|
|
427
|
-
|
|
428
|
-
let
|
|
429
|
-
|
|
430
|
-
}, []), B = t(() => {
|
|
431
|
-
w.current?.pause(), j(!1);
|
|
432
|
-
}, []), V = t(() => {
|
|
433
|
-
w.current?.isPlaying() ? B() : z();
|
|
434
|
-
}, [B, z]), H = t(() => {
|
|
435
|
-
w.current?.reset(), j(!1);
|
|
436
|
-
}, []), U = t(() => {
|
|
437
|
-
R.current = !0, L((e) => e + 1);
|
|
675
|
+
var V = e(({ src: e, autoPlay: c = !0, showControls: l = !1, debug: u = !1, loopCount: d, className: f, style: p, width: m, height: h, onPlay: g, onPause: _, onEnd: v, onLoaded: y, onError: b }, x) => {
|
|
676
|
+
let S = i(null), T = i(null), E = i(g), O = i(_), k = i(v), A = i(y), j = i(b), [M, N] = a(c), [P, F] = a(!1), [I, L] = a(null), [R, z] = a(0), V = i(!1), H = i(0);
|
|
677
|
+
E.current = g, O.current = _, k.current = v, A.current = y, j.current = b;
|
|
678
|
+
let U = t(() => {
|
|
679
|
+
T.current?.play(), N(!0);
|
|
438
680
|
}, []), W = t(() => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
681
|
+
T.current?.pause(), N(!1);
|
|
682
|
+
}, []), G = t(() => {
|
|
683
|
+
T.current?.isPlaying() ? W() : U();
|
|
684
|
+
}, [W, U]), K = t(() => {
|
|
685
|
+
T.current?.reset(), N(!1);
|
|
686
|
+
}, []), q = t(() => {
|
|
687
|
+
V.current = !0, z((e) => e + 1);
|
|
688
|
+
}, []), J = t(() => {
|
|
689
|
+
if (!e) return () => {};
|
|
690
|
+
let t = ++H.current, n = !1, r = S.current;
|
|
691
|
+
if (!r) return () => {};
|
|
692
|
+
let i = V.current;
|
|
693
|
+
return V.current = !1, F(!1), N(!1), L(null), T.current?.destroy(), T.current = null, D(r, e, {
|
|
694
|
+
skipPending: i,
|
|
444
695
|
loopCount: d,
|
|
445
696
|
onPlay: () => {
|
|
446
|
-
|
|
697
|
+
t === H.current && N(!0), E.current?.();
|
|
447
698
|
},
|
|
448
699
|
onPause: () => {
|
|
449
|
-
|
|
700
|
+
t === H.current && N(!1), O.current?.();
|
|
450
701
|
},
|
|
451
702
|
onEnd: () => {
|
|
452
|
-
|
|
453
|
-
},
|
|
454
|
-
onLoaded: (e) => {
|
|
455
|
-
F(e), O.current?.(e);
|
|
703
|
+
t === H.current && N(!1), k.current?.();
|
|
456
704
|
}
|
|
457
|
-
}).then((
|
|
458
|
-
if (t) {
|
|
459
|
-
|
|
705
|
+
}).then(({ controller: r, stats: i }) => {
|
|
706
|
+
if (A.current?.(i), n || t !== H.current) {
|
|
707
|
+
r.destroy({ clearCanvas: !1 });
|
|
460
708
|
return;
|
|
461
709
|
}
|
|
462
|
-
|
|
710
|
+
C(e), T.current = r;
|
|
711
|
+
let a = r.destroy.bind(r);
|
|
712
|
+
r.destroy = (t) => {
|
|
713
|
+
a(t), w(e);
|
|
714
|
+
}, F(!0), L(i), c && (r.play(), N(!0));
|
|
463
715
|
}).catch((e) => {
|
|
464
|
-
t
|
|
716
|
+
t === H.current && L(null), j.current?.(e instanceof Error ? e : Error(String(e)));
|
|
465
717
|
}), () => {
|
|
466
|
-
|
|
718
|
+
n = !0, t === H.current && (T.current?.destroy(), T.current = null);
|
|
467
719
|
};
|
|
468
720
|
}, [
|
|
469
721
|
e,
|
|
@@ -471,19 +723,19 @@ var C = e(({ src: e, autoPlay: c = !0, showControls: l = !1, debug: u = !1, loop
|
|
|
471
723
|
c
|
|
472
724
|
]);
|
|
473
725
|
return r(x, () => ({
|
|
474
|
-
play:
|
|
475
|
-
pause:
|
|
476
|
-
toggle:
|
|
477
|
-
reset:
|
|
478
|
-
reload:
|
|
479
|
-
isPlaying: () =>
|
|
726
|
+
play: U,
|
|
727
|
+
pause: W,
|
|
728
|
+
toggle: G,
|
|
729
|
+
reset: K,
|
|
730
|
+
reload: q,
|
|
731
|
+
isPlaying: () => T.current?.isPlaying() ?? !1
|
|
480
732
|
}), [
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
]), n(() =>
|
|
733
|
+
U,
|
|
734
|
+
W,
|
|
735
|
+
G,
|
|
736
|
+
K,
|
|
737
|
+
q
|
|
738
|
+
]), n(() => J(), [J, R]), /* @__PURE__ */ s("div", {
|
|
487
739
|
className: [
|
|
488
740
|
"gif-player",
|
|
489
741
|
l && "gif-player--show-controls",
|
|
@@ -492,26 +744,27 @@ var C = e(({ src: e, autoPlay: c = !0, showControls: l = !1, debug: u = !1, loop
|
|
|
492
744
|
style: p,
|
|
493
745
|
children: [
|
|
494
746
|
/* @__PURE__ */ o("canvas", {
|
|
495
|
-
ref:
|
|
747
|
+
ref: S,
|
|
496
748
|
className: "gif-player__media",
|
|
497
749
|
role: "img",
|
|
498
750
|
style: {
|
|
499
751
|
...m === void 0 ? {} : { width: m },
|
|
500
752
|
...h === void 0 ? {} : { height: h },
|
|
501
|
-
...
|
|
753
|
+
...P ? {} : { visibility: "hidden" }
|
|
502
754
|
}
|
|
503
755
|
}),
|
|
504
|
-
u &&
|
|
505
|
-
|
|
506
|
-
|
|
756
|
+
u && I !== null && P && /* @__PURE__ */ o(B, {
|
|
757
|
+
stats: I,
|
|
758
|
+
canvasRef: S,
|
|
759
|
+
visible: P
|
|
507
760
|
}),
|
|
508
|
-
l &&
|
|
761
|
+
l && P && /* @__PURE__ */ o("div", {
|
|
509
762
|
className: "gif-player__controls",
|
|
510
763
|
children: /* @__PURE__ */ o("button", {
|
|
511
764
|
type: "button",
|
|
512
765
|
className: "gif-player__btn",
|
|
513
|
-
onClick:
|
|
514
|
-
children:
|
|
766
|
+
onClick: G,
|
|
767
|
+
children: M ? /* @__PURE__ */ s("svg", {
|
|
515
768
|
viewBox: "0 0 24 24",
|
|
516
769
|
"aria-hidden": "true",
|
|
517
770
|
children: [/* @__PURE__ */ o("rect", {
|
|
@@ -537,6 +790,6 @@ var C = e(({ src: e, autoPlay: c = !0, showControls: l = !1, debug: u = !1, loop
|
|
|
537
790
|
]
|
|
538
791
|
});
|
|
539
792
|
});
|
|
540
|
-
|
|
793
|
+
V.displayName = "GifPlayer";
|
|
541
794
|
//#endregion
|
|
542
|
-
export {
|
|
795
|
+
export { V as GifPlayer, T as clearGifResourceCache, D as createGifController, A as formatGifLoadStats, j as formatLoadTimeMs, k as getGifLoadStatsView, O as getTotalLoadTimeMs };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { GifPlayer } from './components/GifPlayer';
|
|
2
2
|
export type { GifPlayerComponent, GifPlayerProps, GifPlayerRef, } from './components/GifPlayer';
|
|
3
|
-
export { createGifController } from './utils';
|
|
4
|
-
export type { CreateGifOptions, GifController } from './utils';
|
|
3
|
+
export { createGifController, clearGifResourceCache, formatGifLoadStats, formatLoadTimeMs, getGifLoadStatsView, getTotalLoadTimeMs } from './utils';
|
|
4
|
+
export type { CreateGifOptions, GifController, GifLoadStats, GifLoadStatsLine, GifLoadStatsMode, GifLoadStatsView } from './utils';
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import type { CreateGifOptions, GifController } from './types';
|
|
2
|
-
export declare function createGifController(canvas: HTMLCanvasElement, src: string, options?: CreateGifOptions): Promise<
|
|
1
|
+
import type { CreateGifOptions, GifController, GifLoadStats } from './types';
|
|
2
|
+
export declare function createGifController(canvas: HTMLCanvasElement, src: string, options?: CreateGifOptions): Promise<{
|
|
3
|
+
controller: GifController;
|
|
4
|
+
stats: GifLoadStats;
|
|
5
|
+
}>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { LoadedGif } from './types';
|
|
1
|
+
import type { LoadedGif, GifLoadStats } from './types';
|
|
2
2
|
interface LoadGifResourceOptions {
|
|
3
|
-
/** reload 等场景:不复用进行中的请求,直接发起新 fetch */
|
|
4
3
|
skipPending?: boolean;
|
|
5
4
|
}
|
|
6
5
|
interface LoadGifResult {
|
|
7
6
|
gif: LoadedGif;
|
|
8
|
-
|
|
7
|
+
stats: GifLoadStats;
|
|
9
8
|
}
|
|
10
9
|
export declare function loadGifResource(src: string, options?: LoadGifResourceOptions): Promise<LoadGifResult>;
|
|
10
|
+
export declare function acquireGifResource(src: string): void;
|
|
11
11
|
export declare function releaseGifResource(src: string): void;
|
|
12
12
|
export declare function clearGifResourceCache(): void;
|
|
13
13
|
export {};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { createGifController } from './gifController';
|
|
2
|
-
export
|
|
2
|
+
export { acquireGifResource, clearGifResourceCache, releaseGifResource } from './gifResourceManager';
|
|
3
|
+
export { formatGifLoadStats, formatGifLoadStatsCompact, formatLoadTimeMs, getDebugDensity, getGifLoadStatsView, getTotalLoadTimeMs } from './loadStats';
|
|
4
|
+
export type { GifLoadStatsLine, GifLoadStatsMode, GifLoadStatsView } from './loadStats';
|
|
5
|
+
export type { CreateGifOptions, GifController, GifLoadStats } from './types';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GifLoadStats } from './types';
|
|
2
|
+
export declare function getTotalLoadTimeMs(stats: GifLoadStats): number;
|
|
3
|
+
export type GifLoadStatsMode = 'fresh' | 'pending' | 'cache';
|
|
4
|
+
export interface GifLoadStatsLine {
|
|
5
|
+
label: string;
|
|
6
|
+
valueMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface GifLoadStatsView {
|
|
9
|
+
mode: GifLoadStatsMode;
|
|
10
|
+
lines: GifLoadStatsLine[];
|
|
11
|
+
totalMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function getGifLoadStatsView(stats: GifLoadStats): GifLoadStatsView;
|
|
14
|
+
export declare function formatGifLoadStats(stats: GifLoadStats): string;
|
|
15
|
+
export declare function formatLoadTimeMs(ms: number): string;
|
|
16
|
+
export declare function getDebugDensity(width: number, height: number): 'compact' | 'medium' | 'full';
|
|
17
|
+
export declare function formatGifLoadStatsCompact(stats: GifLoadStats): string;
|
|
18
|
+
export declare function getGifLoadStatsLineLabel(line: GifLoadStatsLine, mode: GifLoadStatsMode): string;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -4,20 +4,33 @@ export interface LoadedGif {
|
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
6
|
}
|
|
7
|
+
export interface GifLoadStats {
|
|
8
|
+
/** 本次实际 fetch 耗时,仅 fresh 发起方有值 */
|
|
9
|
+
fetchTimeMs: number;
|
|
10
|
+
/** 本次实际 decode 耗时,仅 fresh 发起方有值 */
|
|
11
|
+
decodeTimeMs: number;
|
|
12
|
+
/** 等待进行中的 fetch 阶段,仅 pending */
|
|
13
|
+
pendingWaitFetchMs: number;
|
|
14
|
+
/** 等待进行中的 decode 阶段,仅 pending */
|
|
15
|
+
pendingWaitDecodeMs: number;
|
|
16
|
+
fromCache: boolean;
|
|
17
|
+
fromPending: boolean;
|
|
18
|
+
}
|
|
7
19
|
export interface CreateGifOptions {
|
|
8
20
|
loopCount?: number;
|
|
9
21
|
onEnd?: () => void;
|
|
10
22
|
onPlay?: () => void;
|
|
11
23
|
onPause?: () => void;
|
|
12
|
-
onLoaded?: (
|
|
13
|
-
/** reload 等场景:不复用进行中的请求,直接发起新 fetch */
|
|
24
|
+
onLoaded?: (stats: GifLoadStats) => void;
|
|
14
25
|
skipPending?: boolean;
|
|
15
26
|
}
|
|
16
27
|
export interface GifController {
|
|
17
28
|
play: () => void;
|
|
18
29
|
pause: () => void;
|
|
19
30
|
reset: () => void;
|
|
20
|
-
destroy: (
|
|
31
|
+
destroy: (options?: {
|
|
32
|
+
clearCanvas?: boolean;
|
|
33
|
+
}) => void;
|
|
21
34
|
isPlaying: () => boolean;
|
|
22
35
|
getCompletedLoops: () => number;
|
|
23
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libshub/gif-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"module": "./dist/gif-tools.es.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "vite",
|
|
29
29
|
"build": "vite build && tsc -p tsconfig.lib.json",
|
|
30
|
+
"build:example": "vite build --config vite.example.config.ts",
|
|
31
|
+
"preview:example": "vite preview --config vite.example.config.ts",
|
|
30
32
|
"lint": "eslint .",
|
|
31
33
|
"preview": "vite preview"
|
|
32
34
|
},
|
|
@@ -53,4 +55,4 @@
|
|
|
53
55
|
"dependencies": {
|
|
54
56
|
"gifuct-js": "^2.1.2"
|
|
55
57
|
}
|
|
56
|
-
}
|
|
58
|
+
}
|