A lightweight, high-performance Canvas rendering engine built with TypeScript and React. It provides a declarative API to build complex canvas scenes using standard React components.
- Declarative API: Compose scenes using React components like
<Rect>,<Circle>,<Group>. - Scene Graph: Built-in hierarchical object management (parent-child relationships).
- Interactive: Support for click, hover, and drag events (
onClick,onMouseEnter,onDragStart, etc.). - Smart Rendering: Z-Index sorting, Group clipping, and optimized rendering loop using
requestAnimationFrame. - High DPI Support: Automatically handles retina/high-density displays.
- TypeScript: Written in TypeScript with full type definitions.
- Animation Hooks:
useFramehook for smooth, frame-by-frame animations.
- Clone the repository.
- Install dependencies:
npm install
- Start the development server:
npm run dev
The engine supports basic shapes like Rectangle, Circle, Text, Line, Path (SVG), and Image.
import Canvas from './src/react/Canvas';
import { Rect, Circle, Text, Line, Image, Path } from './src/react/Shapes';
const App = () => (
<Canvas width={800} height={600}>
{/* Basic Rectangle */}
<Rect x={10} y={10} width={100} height={50} fill="red" />
{/* Circle */}
<Circle x={200} y={100} radius={30} fill="blue" />
{/* Text */}
<Text text="Hello World" x={10} y={100} fontSize={24} fill="#333" />
{/* SVG Path */}
<Path
data="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z"
fill="pink"
stroke="red"
/>
</Canvas>
);The engine supports mouse and drag events on all shapes.
<Rect
x={100}
y={100}
width={50}
height={50}
fill="orange"
onClick={(e) => console.log('Clicked!', e)}
onMouseEnter={() => console.log('Hover enter')}
onMouseLeave={() => console.log('Hover leave')}
draggable={true}
onDragStart={() => console.log('Drag started')}
onDragMove={(e) => console.log('Dragging...', e)}
onDragEnd={() => console.log('Drag ended')}
/>Use <Group> to organize shapes. You can also use clip to restrict rendering to the group's bounding box, and zIndex to control rendering order.
import { Group, Rect } from './src/react/Shapes';
const Scene = () => (
<Group x={400} y={300} clip={true} clipWidth={200} clipHeight={200}>
{/* Background (z-index 0) */}
<Rect width={200} height={200} fill="#eee" zIndex={0} />
{/* Foreground (z-index 10) - will be rendered on top */}
<Rect x={50} y={50} width={100} height={100} fill="red" zIndex={10} />
</Group>
);Use the useFrame hook to update component state on every frame.
import { useState } from 'react';
import { useFrame } from './src/react/CanvasContext';
import { Rect } from './src/react/Shapes';
const RotatingBox = () => {
const [rotation, setRotation] = useState(0);
useFrame((time) => {
// time is the elapsed time in milliseconds
setRotation(time * 0.001);
});
return (
<Rect
x={250}
y={250}
width={100}
height={100}
rotation={rotation}
fill="purple"
/>
);
};The root container for the scene.
width: number (default: 500)height: number (default: 500)style: CSSPropertiesonClick,onMouseDown,onMouseUp,onMouseMove,onMouseLeave: Global event handlers
x: number (default: 0)y: number (default: 0)rotation: number (radians, default: 0)scaleX: number (default: 1)scaleY: number (default: 1)zIndex: number (default: 0) - Higher values render on topdraggable: boolean (default: false)cursor: string (default: 'default') - CSS cursor style on hover (e.g. 'pointer', 'grab')filter: string - CSS filter string (e.g. 'blur(5px)')shadowColor: stringshadowBlur: numbershadowOffsetX: numbershadowOffsetY: number- Events:
onClick,onMouseEnter,onMouseLeave,onDragStart,onDragMove,onDragEnd
width: numberheight: numberfill: string (color)
radius: numberfill: string
text: stringfontSize: numberfontFamily: stringfill: stringwidth: number (max width for wrapping)align: 'left' | 'center' | 'right'lineHeight: number
data: string (SVG Path Data)fill: stringstroke: stringlineWidth: number
points: number[] (Array of coordinates[x1, y1, x2, y2, ...])stroke: string (color)lineWidth: numberlineCap: 'butt' | 'round' | 'square'lineJoin: 'bevel' | 'round' | 'miter'closed: boolean (close the path)
src: string (image URL)width: numberheight: number
clip: boolean (enable clipping)clipX,clipY,clipWidth,clipHeight: Clipping rectangle definition
target: Node (the node to attach to)borderStroke: string (color of the border)handleFill: string (color of the handles)keepRatio: boolean (maintain aspect ratio)
Built-in AssetManager handles efficient loading, caching, and deduplication of image resources. <Image> components automatically leverage this system.
Support for real-time visual effects on any node.
<Image
src="photo.png"
filter="blur(5px) grayscale(50%)"
/>
<Rect
fill="white"
shadowColor="black"
shadowBlur={10}
/>Automatic cursor updates based on interaction state.
- Hovering over draggable objects shows
grabcursor. - Dragging shows
grabbingcursor. - Transformer handles show appropriate resize cursors (rotatable).
- Custom cursors via
cursorprop.
Support for automatic text wrapping and alignment.
<Text
text="This is a long text that wraps automatically."
width={200}
align="center"
/>A UI component for interactive scaling and rotation of nodes.
<Transformer target={selectedNode} />Full support for touch interactions (drag, click) on mobile devices.
- Frustum Culling: Automatically skips rendering of objects outside the viewport.
- Smart Dirty Rect: Optimized rendering loop.
src/
├── core/ # Engine Core (Scene Graph, Node, Container, Matrix, Event System)
├── shapes/ # Shape Implementations (Rect, Circle, Path, etc.)
├── react/ # React Bindings (Canvas, Shapes, Context)
└── types/ # Type Definitions
examples/ # Demo Application
Run the test suite using Vitest:
npm test