Skip to content

Commit

Permalink
chore: Add animation methods
Browse files Browse the repository at this point in the history
  • Loading branch information
activeguild committed Dec 12, 2023
1 parent 6ee25cc commit 6f7d660
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 18 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
# animation-texture

A library supports image animation using `react-three-fiber`. The target files are `APNG` and `GIF` files. <br/>
If there is only one frame, a still image is displayed.
If there is only one frame, a still image is displayed.<br/>
The animate, pause, and reset methods can be called from the texture.

## Install

Expand Down
19 changes: 17 additions & 2 deletions example/vite/src/AnimationTexture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { useAnimationTexture } from "animation-texture";
interface Props {
url: string;
position: THREE.Vector3;
isPlaying?: boolean;
}

export function AnimationTexture({ url, position }: Props) {
const { animationTexture } = useAnimationTexture({ url });
export function AnimationTexture({ url, position, isPlaying = true }: Props) {
const { animationTexture } = useAnimationTexture({
url,
});
const meshRef =
useRef<
THREE.Mesh<
Expand All @@ -25,6 +28,18 @@ export function AnimationTexture({ url, position }: Props) {
}
}, [animationTexture]);

useEffect(() => {
if (!animationTexture) {
return;
}

if (isPlaying) {
animationTexture.animate();
} else {
animationTexture.pause();
}
}, [animationTexture, isPlaying]);

return (
<mesh ref={meshRef} position={position}>
<planeGeometry args={[1, 1]} />
Expand Down
16 changes: 14 additions & 2 deletions example/vite/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Suspense } from "react";
import { Suspense, useState } from "react";
import { Canvas } from "@react-three/fiber";
import {
OrbitControls,
Expand All @@ -9,6 +9,7 @@ import { AnimationTexture } from "./AnimationTexture";
import { preLoad } from "animation-texture";

export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
const framePngUrl = "/frame.png";
const framesPngUrl = "/frames.png";
const frameGifUrl = "/frame.gif";
Expand All @@ -18,11 +19,19 @@ export default function App() {
preLoad(frameGifUrl);
preLoad(framesGifUrl);

const handleAnimate = () => {
setIsPlaying(true);
};

const handlePause = () => {
setIsPlaying(false);
};

return (
<>
<Suspense fallback={null}>
<Canvas
style={{ position: "absolute" }}
style={{ position: "absolute", top: 100 }}
camera={{ position: [-5, 2, 10], fov: 60 }}
flat={false}
>
Expand All @@ -40,6 +49,7 @@ export default function App() {
<AnimationTexture
url={framesPngUrl}
position={new THREE.Vector3(1, 0, 0)}
isPlaying={isPlaying}
/>
<AnimationTexture
url={frameGifUrl}
Expand All @@ -56,6 +66,8 @@ export default function App() {
/>
</Canvas>
</Suspense>
<button onClick={handleAnimate}>animate</button>
<button onClick={handlePause}>pause</button>
</>
);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"scripts": {
"build": "vite build && tsc",
"watch": "vite build --watch",
"watch": "tsc && vite build --watch",
"local:publish": "npm link"
},
"dependencies": {
Expand Down
42 changes: 42 additions & 0 deletions src/AnimationTexture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Texture, LinearFilter } from "three";

class AnimationTexture extends Texture {
isAnimationTexture: boolean;
animate: () => void;
pause: () => void;
reset: () => void;
constructor(
canvas?,
mapping?,
wrapS?,
wrapT?,
magFilter?,
minFilter?,
format?,
type?,
anisotropy?
) {
super(
canvas,
mapping,
wrapS,
wrapT,
magFilter,
minFilter,
format,
type,
anisotropy
);

this.isAnimationTexture = true;
this.animate = () => {};
this.pause = () => {};
this.reset = () => {};
this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
this.generateMipmaps = false;
this.needsUpdate = true;
}
}

export { AnimationTexture };
36 changes: 24 additions & 12 deletions src/useAnimationTexture.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { useCallback, useEffect, useRef, useState } from "react";
import framesWorker from "./worker.js?worker&inline";
import { CanvasTexture, LinearFilter } from "three";
import { LinearFilter } from "three";
import { AnimationTexture } from "./AnimationTexture";

interface UseAnimationTextureArgs {
url: string;
enabledInterval?: boolean;
// enabledInterval?: boolean;
interval?: number;
enabledLoop?: boolean;
loop?: boolean;
autoplay?: boolean;
}

const DEFAULT_ENABLED_INTERVAL = true;
const DEFAULT_INTERVAL = 100;
const DEFAULT_ENABLED_LOOP = true;
const DEFAULT_AUTOPLAY = true;

const framesMap = new Map<
string,
Expand Down Expand Up @@ -106,20 +108,21 @@ const initializeWorker = () => {

export const useAnimationTexture = ({
url,
enabledInterval = DEFAULT_ENABLED_INTERVAL,
interval = DEFAULT_INTERVAL,
enabledLoop = DEFAULT_ENABLED_LOOP,
loop = DEFAULT_ENABLED_LOOP,
autoplay = DEFAULT_AUTOPLAY,
}: UseAnimationTextureArgs) => {
const [animationTexture, setAnimationTexture] =
useState<THREE.CanvasTexture | null>(null);
useState<AnimationTexture | null>(null);
const [currentFrame, setCurrentFrame] = useState(0);
const isGif = url.endsWith(".gif");
const needsDisposal = useRef(false);
const [playing, setPlaying] = useState(autoplay);
const frameUpdate = useCallback(() => {
const currentFrames = getFrameses(url);
if (
animationTexture &&
!enabledLoop &&
!loop &&
currentFrames &&
currentFrame + 1 === currentFrames.images.length
) {
Expand All @@ -135,7 +138,16 @@ export const useAnimationTexture = ({
const frame = currentFrames.frames[currentFrame];
if (!animationTexture) {
currentFrames.ctx.putImageData(image, 0, 0);
const texture = new CanvasTexture(currentFrames.canvas);
const texture = new AnimationTexture(currentFrames.canvas);
texture.animate = () => {
setPlaying(true);
};
texture.pause = () => {
setPlaying(false);
};
texture.reset = () => {
setCurrentFrame(0);
};
texture.premultiplyAlpha = true;
texture.minFilter = LinearFilter;
setAnimationTexture(texture);
Expand All @@ -161,20 +173,20 @@ export const useAnimationTexture = ({
const nextCurrentFrame = (currentFrame + 1) % currentFrames.images.length;
setCurrentFrame(nextCurrentFrame);
}
}, [animationTexture, currentFrame, enabledLoop, isGif, url]);
}, [animationTexture, currentFrame, loop, isGif, url]);

useEffect(() => {
initializeWorker();
load(url);

const intervalForClear =
enabledInterval &&
playing &&
setInterval(() => requestAnimationFrame(frameUpdate), interval);

return () => {
intervalForClear && clearInterval(intervalForClear);
};
}, [enabledInterval, frameUpdate, interval, url]);
}, [playing, frameUpdate, interval, url]);

useEffect(() => {
return () => {
Expand Down

0 comments on commit 6f7d660

Please sign in to comment.