etch-canvas.tsx
a dynamic canvas which renders custom etches
'use client';
import type p5 from 'p5';
import { useRef, useEffect, useState } from 'react';
import { EtchModel, LignesEtchP5, EtchDimensions } from './etches/types';
import Etches, { EtchKey } from './etches';
interface Props {
etchId: EtchKey;
width?: number;
height?: number;
}
export function EtchCanvas(props: Props) {
return (
<EtchCanvasBase
etch={Etches[props.etchId]}
width={props.width}
height={props.height}
/>
);
}
function EtchCanvasBase({
etch,
width,
height,
}: {
etch: EtchModel;
width?: number;
height?: number;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const [p5Instance, setP5Instance] = useState<LignesEtchP5>();
const locals = useRef<{ [key: string]: any }>({
lock: false,
});
useEffect(() => {
// Ensure we don't create multiple instances
// if rendering concurrently.
const hasLock = locals.current.lock;
const createP5Instance = async () => {
if (!hasLock && !p5Instance && containerRef.current && typeof window) {
const p5 = (await import('p5')).default;
locals.current.lock = true;
const $dimensions = { height, width };
if (!$dimensions.height || !$dimensions.width) {
const rect = containerRef.current.getBoundingClientRect();
$dimensions.width = rect.width;
$dimensions.height = rect.height;
}
const dimensions = $dimensions as { width: number; height: number };
const instance = new p5(
createEtch(dimensions, etch),
containerRef.current
);
// On window resize, also resize the canvas
// so it always fits within the container
instance.windowResized = () => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
instance.resizeCanvas(rect.width, rect.height);
// @ts-ignore - custom method
instance.onResize && instance.onResize();
}
};
setP5Instance(instance);
}
};
createP5Instance();
return () => {
p5Instance?.exit && p5Instance.exit();
p5Instance?.remove();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [height, width, etch]);
return <div style={{ width: '100%', height: '100%' }} ref={containerRef} />;
}
function createEtch(params: EtchDimensions, model: EtchModel) {
return (p: p5) => model.render({ p, ...params });
}