react-gif-timeline 0.0.1 → 0.1.0
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/dist/index.d.ts +23 -1
- package/dist/index.js +52 -35
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ interface GifTimelineResult {
|
|
|
15
15
|
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
16
16
|
currentFrame: number;
|
|
17
17
|
totalFrames: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
18
20
|
isLoaded: boolean;
|
|
19
21
|
error: string | null;
|
|
20
22
|
isTransitioning: boolean;
|
|
@@ -27,9 +29,29 @@ interface GifTimelineProps<A extends Anchors> extends GifTimelineOptions<A> {
|
|
|
27
29
|
renderError?: (error: string) => ReactNode;
|
|
28
30
|
children?: (result: GifTimelineResult) => ReactNode;
|
|
29
31
|
}
|
|
32
|
+
interface GifFrameOptions<A extends Anchors> {
|
|
33
|
+
src: string | Blob;
|
|
34
|
+
anchors: A;
|
|
35
|
+
activeState: ActiveState<A>;
|
|
36
|
+
speed?: number;
|
|
37
|
+
onTransitionStart?: (fromFrame: number, toFrame: number) => void;
|
|
38
|
+
onTransitionEnd?: (state: ActiveState<A>) => void;
|
|
39
|
+
}
|
|
40
|
+
interface GifFrameResult {
|
|
41
|
+
currentFrame: number;
|
|
42
|
+
totalFrames: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
isLoaded: boolean;
|
|
46
|
+
error: string | null;
|
|
47
|
+
isTransitioning: boolean;
|
|
48
|
+
transitionProgress: number;
|
|
49
|
+
}
|
|
30
50
|
|
|
31
51
|
declare function GifTimeline<A extends Anchors>(props: GifTimelineProps<A>): react_jsx_runtime.JSX.Element;
|
|
32
52
|
|
|
53
|
+
declare function useGifFrame<A extends Anchors>(options: GifFrameOptions<A>): GifFrameResult;
|
|
54
|
+
|
|
33
55
|
declare function useGifTimeline<A extends Anchors>(options: GifTimelineOptions<A>): GifTimelineResult;
|
|
34
56
|
|
|
35
|
-
export { type ActiveState, type Anchors, GifTimeline, type GifTimelineOptions, type GifTimelineProps, type GifTimelineResult, useGifTimeline };
|
|
57
|
+
export { type ActiveState, type Anchors, type GifFrameOptions, type GifFrameResult, GifTimeline, type GifTimelineOptions, type GifTimelineProps, type GifTimelineResult, useGifFrame, useGifTimeline };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
// src/useGifTimeline.ts
|
|
2
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/frameRenderer.ts
|
|
5
|
+
function initCanvas(canvas, width, height) {
|
|
6
|
+
const dpr = window.devicePixelRatio || 1;
|
|
7
|
+
canvas.width = width * dpr;
|
|
8
|
+
canvas.height = height * dpr;
|
|
9
|
+
const ctx = canvas.getContext("2d");
|
|
10
|
+
ctx.scale(dpr, dpr);
|
|
11
|
+
}
|
|
12
|
+
function renderFrame(canvas, frame, width, height) {
|
|
13
|
+
const ctx = canvas.getContext("2d");
|
|
14
|
+
const temp = document.createElement("canvas");
|
|
15
|
+
temp.width = width;
|
|
16
|
+
temp.height = height;
|
|
17
|
+
const tempCtx = temp.getContext("2d");
|
|
18
|
+
tempCtx.putImageData(frame, 0, 0);
|
|
19
|
+
ctx.clearRect(0, 0, width, height);
|
|
20
|
+
ctx.drawImage(temp, 0, 0, width, height);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/useGifCore.ts
|
|
2
24
|
import { useEffect, useRef, useState } from "react";
|
|
3
25
|
|
|
4
26
|
// src/animator.ts
|
|
@@ -40,27 +62,6 @@ function animate(fromFrame, toFrame, delays, speed, onFrame, onComplete) {
|
|
|
40
62
|
};
|
|
41
63
|
}
|
|
42
64
|
|
|
43
|
-
// src/frameRenderer.ts
|
|
44
|
-
function initCanvas(canvas, width, height) {
|
|
45
|
-
const dpr = window.devicePixelRatio || 1;
|
|
46
|
-
canvas.width = width * dpr;
|
|
47
|
-
canvas.height = height * dpr;
|
|
48
|
-
canvas.style.width = `${width}px`;
|
|
49
|
-
canvas.style.height = `${height}px`;
|
|
50
|
-
const ctx = canvas.getContext("2d");
|
|
51
|
-
ctx.scale(dpr, dpr);
|
|
52
|
-
}
|
|
53
|
-
function renderFrame(canvas, frame, width, height) {
|
|
54
|
-
const ctx = canvas.getContext("2d");
|
|
55
|
-
const temp = document.createElement("canvas");
|
|
56
|
-
temp.width = width;
|
|
57
|
-
temp.height = height;
|
|
58
|
-
const tempCtx = temp.getContext("2d");
|
|
59
|
-
tempCtx.putImageData(frame, 0, 0);
|
|
60
|
-
ctx.clearRect(0, 0, width, height);
|
|
61
|
-
ctx.drawImage(temp, 0, 0, width, height);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
65
|
// src/gifParser.ts
|
|
65
66
|
import { decompressFrames, parseGIF } from "gifuct-js";
|
|
66
67
|
async function parseGifSource(src) {
|
|
@@ -100,16 +101,15 @@ async function parseGifSource(src) {
|
|
|
100
101
|
return { frames, delays, width, height };
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
// src/
|
|
104
|
+
// src/useGifCore.ts
|
|
104
105
|
function resolveAnchorFrame(anchors, activeState) {
|
|
105
106
|
if (Array.isArray(anchors)) {
|
|
106
107
|
return anchors[activeState];
|
|
107
108
|
}
|
|
108
109
|
return anchors[activeState];
|
|
109
110
|
}
|
|
110
|
-
function
|
|
111
|
+
function useGifCore(options) {
|
|
111
112
|
const { src, anchors, activeState, speed = 1, onTransitionStart, onTransitionEnd } = options;
|
|
112
|
-
const canvasRef = useRef(null);
|
|
113
113
|
const gifDataRef = useRef(null);
|
|
114
114
|
const currentFrameRef = useRef(0);
|
|
115
115
|
const animatorRef = useRef(null);
|
|
@@ -134,16 +134,10 @@ function useGifTimeline(options) {
|
|
|
134
134
|
parseGifSource(src).then((data) => {
|
|
135
135
|
if (cancelled) return;
|
|
136
136
|
gifDataRef.current = data;
|
|
137
|
-
if (canvasRef.current) {
|
|
138
|
-
initCanvas(canvasRef.current, data.width, data.height);
|
|
139
|
-
}
|
|
140
137
|
const initialFrame = resolveAnchorFrame(anchorsRef.current, activeStateRef.current);
|
|
141
138
|
const clampedFrame = Math.min(Math.max(0, initialFrame), data.frames.length - 1);
|
|
142
139
|
currentFrameRef.current = clampedFrame;
|
|
143
140
|
setCurrentFrame(clampedFrame);
|
|
144
|
-
if (canvasRef.current) {
|
|
145
|
-
renderFrame(canvasRef.current, data.frames[clampedFrame], data.width, data.height);
|
|
146
|
-
}
|
|
147
141
|
setIsLoaded(true);
|
|
148
142
|
}).catch((err) => {
|
|
149
143
|
if (cancelled) return;
|
|
@@ -185,16 +179,13 @@ function useGifTimeline(options) {
|
|
|
185
179
|
speed,
|
|
186
180
|
(frameIndex) => {
|
|
187
181
|
currentFrameRef.current = frameIndex;
|
|
188
|
-
|
|
189
|
-
renderFrame(canvasRef.current, data.frames[frameIndex], data.width, data.height);
|
|
190
|
-
}
|
|
182
|
+
setCurrentFrame(frameIndex);
|
|
191
183
|
if (frameIndex !== fromFrame) {
|
|
192
184
|
stepsDone++;
|
|
193
185
|
setTransitionProgress(totalSteps > 0 ? stepsDone / totalSteps : 1);
|
|
194
186
|
}
|
|
195
187
|
},
|
|
196
188
|
() => {
|
|
197
|
-
setCurrentFrame(targetFrame);
|
|
198
189
|
setIsTransitioning(false);
|
|
199
190
|
setTransitionProgress(1);
|
|
200
191
|
onTransitionEndRef.current?.(activeStateRef.current);
|
|
@@ -215,9 +206,11 @@ function useGifTimeline(options) {
|
|
|
215
206
|
};
|
|
216
207
|
}, []);
|
|
217
208
|
return {
|
|
218
|
-
|
|
209
|
+
gifDataRef,
|
|
219
210
|
currentFrame,
|
|
220
211
|
totalFrames: gifDataRef.current?.frames.length ?? 0,
|
|
212
|
+
width: gifDataRef.current?.width ?? 0,
|
|
213
|
+
height: gifDataRef.current?.height ?? 0,
|
|
221
214
|
isLoaded,
|
|
222
215
|
error,
|
|
223
216
|
isTransitioning,
|
|
@@ -225,6 +218,23 @@ function useGifTimeline(options) {
|
|
|
225
218
|
};
|
|
226
219
|
}
|
|
227
220
|
|
|
221
|
+
// src/useGifTimeline.ts
|
|
222
|
+
function useGifTimeline(options) {
|
|
223
|
+
const { gifDataRef, ...result } = useGifCore(options);
|
|
224
|
+
const canvasRef = useRef2(null);
|
|
225
|
+
useEffect2(() => {
|
|
226
|
+
const data = gifDataRef.current;
|
|
227
|
+
if (!canvasRef.current || !data || !result.isLoaded) return;
|
|
228
|
+
initCanvas(canvasRef.current, data.width, data.height);
|
|
229
|
+
}, [result.isLoaded]);
|
|
230
|
+
useEffect2(() => {
|
|
231
|
+
const data = gifDataRef.current;
|
|
232
|
+
if (!canvasRef.current || !data) return;
|
|
233
|
+
renderFrame(canvasRef.current, data.frames[result.currentFrame], data.width, data.height);
|
|
234
|
+
}, [result.currentFrame, result.isLoaded]);
|
|
235
|
+
return { ...result, canvasRef };
|
|
236
|
+
}
|
|
237
|
+
|
|
228
238
|
// src/GifTimeline.tsx
|
|
229
239
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
230
240
|
function GifTimeline(props) {
|
|
@@ -268,7 +278,14 @@ function GifTimeline(props) {
|
|
|
268
278
|
result.isLoaded && children?.(result)
|
|
269
279
|
] });
|
|
270
280
|
}
|
|
281
|
+
|
|
282
|
+
// src/useGifFrame.ts
|
|
283
|
+
function useGifFrame(options) {
|
|
284
|
+
const { gifDataRef: _, ...result } = useGifCore(options);
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
271
287
|
export {
|
|
272
288
|
GifTimeline,
|
|
289
|
+
useGifFrame,
|
|
273
290
|
useGifTimeline
|
|
274
291
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-gif-timeline",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Control GIF animation timelines with React state — map anchor frames to states and animate between them.",
|
|
5
5
|
"sideEffects": false,
|
|
6
|
-
"workspaces": [
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"demo"
|
|
8
|
+
],
|
|
7
9
|
"license": "MIT",
|
|
8
10
|
"type": "module",
|
|
9
11
|
"types": "./dist/index.d.ts",
|