Skip to content

Commit

Permalink
cleanup and better types for canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
prtcl committed Nov 12, 2024
1 parent 79d8172 commit 94bfb6d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 47 deletions.
51 changes: 26 additions & 25 deletions src/lib/canvas/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
useEffect,
forwardRef,
type CanvasHTMLAttributes,
type MutableRefObject,
useEffect,
useMemo,
useRef,
useState,
useMemo,
} from 'react';
import CanvasApi from './lib/CanvasApi';
import { CanvasApi } from './lib/CanvasApi';
import { isCanvasMutableRef, type CanvasRef } from './types';

export const useCanvas = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
Expand All @@ -20,7 +21,7 @@ export const useCanvas = () => {

const canvasProps = useMemo<CanvasProps>(
() => ({
canvasRef,
ref: canvasRef,
onReady: () => {
setState({
canvas: new CanvasApi(canvasRef.current),
Expand All @@ -37,29 +38,29 @@ export const useCanvas = () => {
};
};

type CanvasRef = MutableRefObject<HTMLCanvasElement>;

export interface CanvasProps extends CanvasHTMLAttributes<HTMLCanvasElement> {
canvasRef: CanvasRef;
ref: CanvasRef;
onReady: (ref: CanvasRef) => void;
}

export const Canvas = (props: CanvasProps) => {
const { canvasRef, onReady, ...canvasProps } = props;
const hasInitialized = useRef(false);
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
function Canvas(props, innerRef) {
const { onReady, ...canvasProps } = props;
const hasInitialized = useRef(false);

useEffect(() => {
if (canvasRef.current && !hasInitialized.current) {
onReady(canvasRef);
hasInitialized.current = true;
}
}, [canvasRef, onReady]);
useEffect(() => {
if (isCanvasMutableRef(innerRef)) {
hasInitialized.current = true;
onReady(innerRef);
}
}, [innerRef, onReady]);

return (
<canvas
{...canvasProps}
ref={canvasRef}
style={{ width: '100%', height: '100%' }}
/>
);
};
return (
<canvas
{...canvasProps}
ref={innerRef}
style={{ width: '100%', height: '100%' }}
/>
);
},
);
51 changes: 29 additions & 22 deletions src/lib/canvas/lib/CanvasApi.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import type { Color, Polygon, Rect } from '../types';
import {
type Color,
type Polygon,
type Rect,
type Size,
invariantRenderingContext,
} from '../types';
import { colorToRgba } from './helpers';

export default class CanvasApi {
export class CanvasApi {
ref: HTMLCanvasElement;
context: CanvasRenderingContext2D;
size: { width: number; height: number };
size: Size;

constructor(ref: HTMLCanvasElement) {
const initialRect = ref.getBoundingClientRect();
const context = ref.getContext('2d');
invariantRenderingContext(context);
this.ref = ref;
this.context = ref.getContext('2d');
const rect = ref.getBoundingClientRect();

this.resize(rect);
this.context = context;
this.resize(initialRect);
}

resize = (updatedBounds: DOMRect) => {
const { width, height } = updatedBounds;

const dpr = window.devicePixelRatio || 1;
this.size = { width, height };
this.ref.style.width = `${width}px`;
this.ref.style.height = `${height}px`;
this.ref.width = width;
this.ref.height = height;
this.ref.width = width * dpr;
this.ref.height = height * dpr;
this.scale(dpr, dpr);
};

this.size = {
width,
height,
};
scale = (x: number, y: number) => {
this.context.scale(x, y);
};

clear = (width = this.size.width, height = this.size.height) => {
Expand Down Expand Up @@ -56,22 +64,21 @@ export default class CanvasApi {
drawPolygon = (poly: Polygon, opts?: { shouldFill?: boolean }) => {
const { coords } = poly;
this.context.beginPath();

for (let i = 0; i < coords.length; i += 1) {
const coord = coords[i];

if (i === 0) {
for (const [index, coord] of coords.entries()) {
if (index === 0) {
this.context.moveTo(...coord);
} else {
this.context.lineTo(...coord);
}
}

if (opts?.shouldFill === true) {
if (opts?.shouldFill) {
this.context.fill();
}

this.context.closePath();
this.context.stroke();
};

toDataURL = (type?: string, quality?: number) => {
return this.ref.toDataURL(type, quality);
};
}
37 changes: 37 additions & 0 deletions src/lib/canvas/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { MutableRefObject } from 'react';

export type Color = {
r: number;
g: number;
Expand All @@ -17,3 +19,38 @@ export type Rect = {
width: number;
height: number;
};

export type Size = {
width: number;
height: number;
};

export type CanvasRef = MutableRefObject<HTMLCanvasElement>;

export function isRenderingContext(
value: unknown,
): value is CanvasRenderingContext2D {
return typeof value === 'object' && value !== null && 'canvas' in value;
}

export function invariantRenderingContext(
value: unknown,
): asserts value is CanvasRenderingContext2D {
if (!isRenderingContext(value)) {
throw new Error('Invalid rendering context');
}
}

export function isCanvasMutableRef(value: unknown): value is CanvasRef {
return (
typeof value === 'object' &&
'current' in value &&
value.current instanceof HTMLCanvasElement
);
}

export function invariantCanvasRef(value: unknown) {
if (!isCanvasMutableRef(value)) {
throw new Error('Invalid canvas ref');
}
}

0 comments on commit 94bfb6d

Please sign in to comment.