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>. - React Reconciler: Custom React renderer that maps Fiber tree directly to Canvas Scene Graph, bypassing DOM diffing for maximum performance.
- 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
canvas-react provides a set of common shapes out of the box. All shapes support standard props like x, y, fill, stroke, lineWidth, opacity, rotation, scaleX, scaleY, and event handlers like onClick.
| Shape | Key Props | Description |
|---|---|---|
<Rect /> |
width, height, cornerRadius |
A rectangle with optional rounded corners. |
<Circle /> |
radius |
A simple circle. |
<Ellipse /> |
radiusX, radiusY |
An ellipse defined by two radii. |
<Line /> |
points, closed |
A series of connected points. Set closed for a polygon. |
<Path /> |
data |
Renders SVG path data (e.g., M 10 10 L 90 90). |
<Text /> |
text, fontSize, fontFamily, align, width, wordWrap |
Multiline text with alignment and wrapping support. |
<Image /> |
image, width, height |
Renders an HTMLImageElement. |
<Arc /> |
innerRadius, outerRadius, angle, clockwise |
A wedge or arc segment. |
<RegularPolygon /> |
sides, radius |
A polygon with equal sides (triangle, hexagon, etc.). |
<Star /> |
numPoints, innerRadius, outerRadius |
A star shape with customizable points and radii. |
<Group /> |
clip, clipWidth, clipHeight |
A container for grouping other shapes. Supports clipping. |
<Transformer /> |
nodes |
A special UI component for rotating/scaling other nodes. |
The engine supports a rich set of basic shapes including Rectangle, Circle, Ellipse, RegularPolygon, Star, Arc, Text, Line, Path (SVG), and Image. All shapes support fill and stroke properties.
import Canvas from './src/react/Canvas';
import { Rect, Circle, Ellipse, RegularPolygon, Star, Arc, Text, Line, Image, Path } from './src/react/Shapes';
const App = () => (
<Canvas width={800} height={600}>
{/* Basic Rectangle (supports rounded corners and dashed strokes) */}
<Rect x={10} y={10} width={100} height={50} fill="red" stroke="black" lineWidth={2} cornerRadius={10} lineDash={[5, 5]} />
{/* Circle */}
<Circle x={200} y={100} radius={30} fill="blue" />
{/* Ellipse */}
<Ellipse x={300} y={100} radiusX={40} radiusY={20} fill="green" />
{/* Regular Polygon */}
<RegularPolygon x={400} y={100} sides={6} radius={30} fill="purple" />
{/* Star */}
<Star x={500} y={100} numPoints={5} innerRadius={15} outerRadius={30} fill="yellow" stroke="orange" />
{/* Arc / Sector */}
<Arc x={600} y={100} innerRadius={10} outerRadius={30} angle={Math.PI} fill="cyan" />
{/* Text (supports multiline and vertical alignment) */}
<Text text="Hello World\nLine 2" x={10} y={100} fontSize={24} fill="#333" verticalAlign="middle" />
{/* 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. You can also enable global canvas panning and zooming by setting interactive={true} on the <Canvas>.
<Canvas width={800} height={600} interactive={true}>
<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')}
/>
</Canvas>To make debugging complex canvas scenes easier, the engine provides multi-layered debugging support. Simply enable debug={true} on the <Canvas>:
- React DevTools Integration: When debugging is enabled, you can inspect the live coordinates and properties of all shape nodes (e.g.,
Rect,Circle) directly within the Chrome React DevTools "Components" panel. - Global Variable Exposure: The engine exposes the root scene graph instance to
window.__CANVAS_STAGE__. You can print and manipulate it directly in the Chrome Console (e.g.,__CANVAS_STAGE__.children). - Built-in Visual Panel: An in-app developer tools panel will be rendered in the bottom right corner, displaying the current FPS, node tree, and bounding box highlighters.
<Canvas width={800} height={600} debug={true}>
{/* ... */}
</Canvas>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>
);Besides the useFrame hook, the engine now has a built-in tweening system for declarative animations on any node.
import { useEffect, useRef } from 'react';
import { Rect } from './src/react/Shapes';
import { Node } from './src/core/Node';
const AnimatedBox = () => {
const nodeRef = useRef<Node>(null);
useEffect(() => {
if (nodeRef.current) {
// Smoothly transition to the specified properties over 1 second
nodeRef.current.to({
x: 300,
rotation: Math.PI,
opacity: 0.5,
duration: 1,
easing: (t) => t * (2 - t) // Optional easing function
});
}
}, []);
return <Rect ref={nodeRef} width={100} height={100} fill="purple" />;
};The engine supports exporting the canvas or any node to an image or JSON.
// Export as Image (DataURL)
const dataUrl = canvasRef.current.toDataURL({ mimeType: 'image/png', quality: 1, pixelRatio: 2 });
// Serialize Scene Graph
const json = nodeRef.current.toJSON();The root container for the scene.
width: number (default: 500)height: number (default: 500)style: CSSPropertiesinteractive: boolean (default: false) - Enable pan & zoom interactions on the canvasonClick,onDoubleClick,onMouseDown,onMouseUp,onMouseMove,onMouseLeave,onWheel: Global event handlers- Methods:
toDataURL(options)Export canvas image
x: number (default: 0)y: number (default: 0)rotation: number (radians, default: 0)scaleX: number (default: 1)scaleY: number (default: 1)opacity: number (global alpha)globalCompositeOperation: string (blend mode)zIndex: number (default: 0) - Higher values render on topdraggable: boolean (default: false)cursor: string (default: 'default') - CSS cursor style on hover- Stroke & Fill:
fill,stroke,lineWidth,lineDash,lineDashOffset,lineCap,lineJoin - Filters & Shadows:
filter,shadowColor,shadowBlur,shadowOffsetX,shadowOffsetY - Events:
onClick,onDoubleClick,onMouseDown,onMouseUp,onMouseMove,onMouseEnter,onMouseLeave,onWheel,onDragStart,onDragMove,onDragEnd - Node Methods:
cache(),clearCache(),toJSON(),toDataURL(),to(config)
width: numberheight: numbercornerRadius: number | number[] (rounded corners)
radius: number
radiusX: numberradiusY: number
sides: number (number of sides)radius: number
numPoints: number (number of points)innerRadius: numberouterRadius: number
innerRadius: numberouterRadius: numberangle: number (radians)
text: stringfontSize: numberfontFamily: stringfontStyle,fontWeight,fontVariant: Font stylingwidth: number (max width for wrapping)align: 'left' | 'center' | 'right'verticalAlign: 'top' | 'middle' | 'bottom'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)image: HTMLImageElement (pre-loaded image element)width: numberheight: numberfilters: FilterFunction[] (Pixel-level filters, built-ins:Filters.Grayscale,Filters.Invert,Filters.Sepia,Filters.Brightness)
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