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 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/useGifTimeline.ts
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 useGifTimeline(options) {
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
- if (canvasRef.current) {
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
- canvasRef,
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.1",
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": ["demo"],
6
+ "workspaces": [
7
+ "demo"
8
+ ],
7
9
  "license": "MIT",
8
10
  "type": "module",
9
11
  "types": "./dist/index.d.ts",