Skip to content

Instantly share code, notes, and snippets.

@Shaxadhere
Last active May 15, 2023 08:50
Show Gist options
  • Save Shaxadhere/3e2edf4b96da6266cc039627970f4cfc to your computer and use it in GitHub Desktop.
Save Shaxadhere/3e2edf4b96da6266cc039627970f4cfc to your computer and use it in GitHub Desktop.
animate image sequence via scroll
import { CSSProperties } from "react";
import { clamp, MotionValue, useMotionValueEvent } from "framer-motion";
import React, { useEffect, useRef } from "react";
interface ImageSequenceProps {
progress: MotionValue<number>;
images: string[];
height: number;
width: number;
style?: CSSProperties;
className?: string;
}
const ImageSequence = ({
progress,
images,
width,
height,
style,
className
}: ImageSequenceProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const imgRefs = useRef<HTMLImageElement[]>([]);
const contextRef = useRef<CanvasRenderingContext2D | null | undefined>(null);
const frameRef = useRef<number>();
const setFrame = (rawFrame: number) => {
const frame = clamp(0, images.length - 1, Math.floor(rawFrame));
if (frameRef.current !== frame && contextRef.current) {
frameRef.current = frame;
contextRef.current.drawImage(imgRefs.current[frame], 0, 0);
}
};
useMotionValueEvent(progress, "change", (val) => {
const frame = clamp(0, images.length - 1, Math.floor(val * images.length));
setFrame(frame);
});
useEffect(() => {
contextRef.current = canvasRef.current?.getContext("2d");
imgRefs.current = images.map((src) => {
const img = new Image();
img.src = src;
return img;
});
imgRefs.current[0].onload = () => {
setFrame(0);
};
}, []);
return (
<canvas
ref={canvasRef}
className={className}
style={style}
width={width}
height={height}
/>
);
};
export default ImageSequence;
import { Box, ChakraProvider } from "@chakra-ui/react";
import { useViewportScroll } from "framer-motion";
import * as React from "react";
import { createRoot } from "react-dom/client";
import ImageSequence from "./ImageSequence";
import range from "lodash.range";
import "./styles.css";
const images = range(55).map((i) => {
const paddedIndex = i.toString().padStart(4, "0");
return `https://www.apple.com/105/media/us/airpods-3rd-generation/2021/3c0b27aa-a5fe-4365-a9ae-83c28d10fa21/anim/battery/large/${paddedIndex}.jpg`;
});
function App() {
const { scrollYProgress } = useViewportScroll();
return (
<Box h="1000vh">
<Box pos="sticky" top={0} h="100vh">
<ImageSequence
progress={scrollYProgress}
images={images}
width={1464}
height={824}
style={{
position: "absolute",
width: "100%",
height: "100%",
objectFit: "contain"
}}
/>
</Box>
</Box>
);
}
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<React.StrictMode>
<ChakraProvider>
<App />
</ChakraProvider>
</React.StrictMode>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment