svg-layout-designer-react/src/Components/Canvas/Canvas.tsx

146 lines
3.8 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import { IPoint } from '../../Interfaces/IPoint';
import { BAR_WIDTH } from '../Bar/Bar';
interface ICanvasProps {
width: number
height: number
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
className?: string
style?: React.CSSProperties
};
function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> {
const canvasRef = useRef<HTMLCanvasElement>(null);
const frameCount = useRef(0);
const translatePos = useRef({
x: 0,
y: 0
});
const startDragOffset = useRef({
x: 0,
y: 0
});
const scale = useRef(1.0);
const scaleMultiplier = useRef(0.8);
const dragging = useRef(false);
useEffect(() => {
const canvas = canvasRef.current;
let animationFrameId = 0;
if (canvas === null) {
return;
}
const context = canvas.getContext('2d');
// add event listeners to handle screen drag
function MouseDown(evt: MouseEvent): void {
dragging.current = true;
startDragOffset.current.x = evt.clientX - translatePos.current.x;
startDragOffset.current.y = evt.clientY - translatePos.current.y;
}
function MouseUp(evt: MouseEvent): void {
dragging.current = false;
}
function MouseMove(evt: MouseEvent): void {
if (!dragging.current || context === null) {
return;
}
translatePos.current.x = evt.clientX - startDragOffset.current.x;
translatePos.current.y = evt.clientY - startDragOffset.current.y;
frameCount.current++;
draw(context, frameCount.current, scale.current, translatePos.current);
}
function HandleScroll(evt: WheelEvent): void {
if (context === null) {
return;
}
if (evt.deltaY >= 0) {
scale.current *= scaleMultiplier.current;
} else {
scale.current /= scaleMultiplier.current;
}
draw(context, frameCount.current, scale.current, translatePos.current);
evt.preventDefault();
}
canvas.addEventListener('mousedown', MouseDown);
window.addEventListener('mouseup', MouseUp);
canvas.addEventListener('mousemove', MouseMove);
canvas.addEventListener('wheel', HandleScroll);
function Render(): void {
if (context === null) {
return;
}
frameCount.current++;
draw(context, frameCount.current, scale.current, translatePos.current);
animationFrameId = window.requestAnimationFrame(Render);
}
Render();
return () => {
window.cancelAnimationFrame(animationFrameId);
canvas.removeEventListener('mousedown', MouseDown);
window.removeEventListener('mouseup', MouseUp);
canvas.removeEventListener('mousemove', MouseMove);
canvas.removeEventListener('wheel', HandleScroll);
};
}, [draw]);
return canvasRef;
}
function UseSVGAutoResizer(
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
): void {
React.useEffect(() => {
function OnResize(): void {
setViewer({
viewerWidth: window.innerWidth - BAR_WIDTH,
viewerHeight: window.innerHeight
});
}
window.addEventListener('resize', OnResize);
return () => {
window.removeEventListener('resize', OnResize);
};
});
}
interface Viewer {
viewerWidth: number
viewerHeight: number
}
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
const canvasRef = UseCanvas(draw);
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
viewerWidth: width,
viewerHeight: height
});
UseSVGAutoResizer(setViewer);
return (
<canvas
ref={canvasRef}
style={style}
width={viewerWidth}
height={viewerHeight}
className={className}
/>
);
}