Merged PR 200: Implement the Canvas API rather than using SVG
This commit is contained in:
parent
af1b32c8d6
commit
04d79688cb
6 changed files with 712 additions and 11 deletions
146
src/Components/Canvas/Canvas.tsx
Normal file
146
src/Components/Canvas/Canvas.tsx
Normal file
|
@ -0,0 +1,146 @@
|
|||
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}
|
||||
/>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue