react-gif-timeline 0.0.2 → 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 CHANGED
@@ -29,9 +29,29 @@ interface GifTimelineProps<A extends Anchors> extends GifTimelineOptions<A> {
29
29
  renderError?: (error: string) => ReactNode;
30
30
  children?: (result: GifTimelineResult) => ReactNode;
31
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
+ }
32
50
 
33
51
  declare function GifTimeline<A extends Anchors>(props: GifTimelineProps<A>): react_jsx_runtime.JSX.Element;
34
52
 
53
+ declare function useGifFrame<A extends Anchors>(options: GifFrameOptions<A>): GifFrameResult;
54
+
35
55
  declare function useGifTimeline<A extends Anchors>(options: GifTimelineOptions<A>): GifTimelineResult;
36
56
 
37
- 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,25 +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
- const ctx = canvas.getContext("2d");
49
- ctx.scale(dpr, dpr);
50
- }
51
- function renderFrame(canvas, frame, width, height) {
52
- const ctx = canvas.getContext("2d");
53
- const temp = document.createElement("canvas");
54
- temp.width = width;
55
- temp.height = height;
56
- const tempCtx = temp.getContext("2d");
57
- tempCtx.putImageData(frame, 0, 0);
58
- ctx.clearRect(0, 0, width, height);
59
- ctx.drawImage(temp, 0, 0, width, height);
60
- }
61
-
62
65
  // src/gifParser.ts
63
66
  import { decompressFrames, parseGIF } from "gifuct-js";
64
67
  async function parseGifSource(src) {
@@ -98,16 +101,15 @@ async function parseGifSource(src) {
98
101
  return { frames, delays, width, height };
99
102
  }
100
103
 
101
- // src/useGifTimeline.ts
104
+ // src/useGifCore.ts
102
105
  function resolveAnchorFrame(anchors, activeState) {
103
106
  if (Array.isArray(anchors)) {
104
107
  return anchors[activeState];
105
108
  }
106
109
  return anchors[activeState];
107
110
  }
108
- function useGifTimeline(options) {
111
+ function useGifCore(options) {
109
112
  const { src, anchors, activeState, speed = 1, onTransitionStart, onTransitionEnd } = options;
110
- const canvasRef = useRef(null);
111
113
  const gifDataRef = useRef(null);
112
114
  const currentFrameRef = useRef(0);
113
115
  const animatorRef = useRef(null);
@@ -132,16 +134,10 @@ function useGifTimeline(options) {
132
134
  parseGifSource(src).then((data) => {
133
135
  if (cancelled) return;
134
136
  gifDataRef.current = data;
135
- if (canvasRef.current) {
136
- initCanvas(canvasRef.current, data.width, data.height);
137
- }
138
137
  const initialFrame = resolveAnchorFrame(anchorsRef.current, activeStateRef.current);
139
138
  const clampedFrame = Math.min(Math.max(0, initialFrame), data.frames.length - 1);
140
139
  currentFrameRef.current = clampedFrame;
141
140
  setCurrentFrame(clampedFrame);
142
- if (canvasRef.current) {
143
- renderFrame(canvasRef.current, data.frames[clampedFrame], data.width, data.height);
144
- }
145
141
  setIsLoaded(true);
146
142
  }).catch((err) => {
147
143
  if (cancelled) return;
@@ -183,16 +179,13 @@ function useGifTimeline(options) {
183
179
  speed,
184
180
  (frameIndex) => {
185
181
  currentFrameRef.current = frameIndex;
186
- if (canvasRef.current) {
187
- renderFrame(canvasRef.current, data.frames[frameIndex], data.width, data.height);
188
- }
182
+ setCurrentFrame(frameIndex);
189
183
  if (frameIndex !== fromFrame) {
190
184
  stepsDone++;
191
185
  setTransitionProgress(totalSteps > 0 ? stepsDone / totalSteps : 1);
192
186
  }
193
187
  },
194
188
  () => {
195
- setCurrentFrame(targetFrame);
196
189
  setIsTransitioning(false);
197
190
  setTransitionProgress(1);
198
191
  onTransitionEndRef.current?.(activeStateRef.current);
@@ -213,7 +206,7 @@ function useGifTimeline(options) {
213
206
  };
214
207
  }, []);
215
208
  return {
216
- canvasRef,
209
+ gifDataRef,
217
210
  currentFrame,
218
211
  totalFrames: gifDataRef.current?.frames.length ?? 0,
219
212
  width: gifDataRef.current?.width ?? 0,
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "react-gif-timeline",
3
- "version": "0.0.2",
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
6
  "workspaces": [