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 { const canvasRef = useRef(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> ): 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({ viewerWidth: width, viewerHeight: height }); UseSVGAutoResizer(setViewer); return ( ); }