speice.io/blog/2024-11-15-playing-with-fire/src/Canvas.tsx

64 lines
1.8 KiB
TypeScript
Raw Normal View History

2024-11-24 18:59:11 -05:00
import React, {useCallback, useEffect, useState} from "react";
import {useColorMode} from "@docusaurus/theme-common";
interface Props {
width: number;
height: number;
2024-11-24 18:59:11 -05:00
painter: Iterator<ImageData>;
children?: React.ReactNode;
}
2024-11-24 18:59:11 -05:00
export default function Canvas({width, height, painter, children}: Props) {
const {colorMode} = useColorMode();
const [image, setImage] = useState<[ImageData]>(null);
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
const canvasRef = useCallback(node => {
if (node !== null) {
setCanvasCtx(node.getContext("2d"));
}
}, []);
const paintImage = new ImageData(width, height);
2024-11-24 18:59:11 -05:00
const paint = () => {
if (!canvasCtx || !image) {
return;
}
2024-11-24 18:59:11 -05:00
for (const [index, value] of image[0].data.entries()) {
if (index % 4 === 3) {
// Alpha values are copied as-is
paintImage.data[index] = value;
} else {
// If dark mode is active, invert the color
paintImage.data[index] = colorMode === 'light' ? value : 255 - value;
}
}
canvasCtx.putImageData(paintImage, 0, 0);
2024-11-24 18:59:11 -05:00
}
useEffect(paint, [colorMode, image]);
const animate = () => {
const nextImage = painter.next().value;
if (nextImage) {
setImage([nextImage])
requestAnimationFrame(animate);
}
}
useEffect(animate, [canvasCtx]);
return (
2024-11-24 18:59:11 -05:00
<>
<canvas
ref={canvasRef}
width={width}
height={height}
style={{
aspectRatio: width / height,
width: '100%'
}}
/>
{children}
2024-11-24 18:59:11 -05:00
</>
)
}