A React-based canvas library for creating pannable, zoomable, and interactive canvas experiences. Originally developed (by me) for the Hack Western 12 Website.
Install the package via npm:
npm install @hunterchen/canvasThis library requires the following peer dependencies:
npm install react react-dom framer-motionYou must import the compiled CSS file in your application's entry point. The library uses pre-compiled Tailwind CSS, so you don't need to install or configure Tailwind yourself.
Note: The library uses the
canvas-prefix for all custom CSS classes and variables to minimize conflicts with your project. Custom colors likecanvas-heavy,canvas-medium,canvas-offwhite, fonts likecanvas-figtree, and utilities likecanvas-backface-hiddenare scoped to avoid naming collisions. You can safely use Tailwind CSS in your own project alongside this library.
In your main application file (e.g., App.tsx, _app.tsx, main.tsx, or index.tsx):
import '@hunterchen/canvas/styles.css';For Next.js:
// pages/_app.tsx
import '@hunterchen/canvas/styles.css';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}For Vite/React:
// main.tsx or App.tsx
import '@hunterchen/canvas/styles.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);import { Canvas, CanvasComponent } from '@hunterchen/canvas';
const homeCoordinates = { x: 0, y: 0, width: 1920, height: 1080 };
function App() {
return (
<Canvas homeCoordinates={homeCoordinates}>
<CanvasComponent offset={homeCoordinates}>
{/* Your section content here */}
</CanvasComponent>
</Canvas>
);
}The Canvas component requires homeCoordinates to define the initial viewport position. Use CanvasComponent to wrap your content sections and position them at specific coordinates on the canvas.
The homeCoordinates prop defines where the canvas initially centers when it loads. This is a required prop that specifies the starting section's position and dimensions:
const homeCoordinates: SectionCoordinates = {
x: 2867, // X position in canvas space
y: 1200, // Y position in canvas space
width: 264, // Section width
height: 800 // Section height
};
<Canvas homeCoordinates={homeCoordinates}>
{/* ... */}
</Canvas>The navItems prop is optional and defines sections that appear in the canvas navbar. Each navigation item specifies a section with its coordinates, label, icon, and whether it's the home section:
import type { NavItem } from '@hunterchen/canvas';
const navItems: NavItem[] = [
{
id: "home",
label: "Home",
icon: "Home", // Lucide icon name or custom component
x: 2867,
y: 1200,
width: 264,
height: 800,
isHome: true // Marks this as the home section
},
{
id: "about",
label: "About",
icon: "Info",
x: 1400,
y: 400,
width: 1013,
height: 800
},
// ... more sections
];
<Canvas homeCoordinates={homeCoordinates} navItems={navItems}>
{/* ... */}
</Canvas>When navItems is provided, the canvas will render a navbar with buttons to navigate between sections. The navbar uses Lucide icons, so make sure the icon names match available Lucide icons.
By default, the canvas uses a size of 6000×4000 pixels. You can customize the canvas dimensions using the canvasWidth and canvasHeight props to create larger or smaller canvas spaces:
<Canvas
homeCoordinates={homeCoordinates}
canvasWidth={8000} // Custom width (default: 6000)
canvasHeight={6000} // Custom height (default: 4000)
>
{/* ... */}
</Canvas>When to customize canvas dimensions:
- Larger canvases (e.g., 10000×8000): When you have many sections spread across a wide area, or want more space for users to explore
- Smaller canvases (e.g., 4000×3000): For simpler layouts with fewer sections, reducing memory usage on lower-end devices
- Custom aspect ratios: Match your content layout needs (e.g., ultra-wide canvases for timeline-style layouts)
Important considerations:
- Canvas coordinates in
homeCoordinates,navItems, andCanvasComponentoffsets should be within your custom canvas bounds - Larger canvases use more memory but provide more space for content
- The canvas automatically handles zoom limits based on your custom dimensions
The canvas comes with neutral gray default backgrounds. You can fully customize the canvas background, intro/wrapper background, and intro content.
The canvas background consists of a gradient, dot pattern, and noise filter. Customize it by passing a canvasBackground prop:
import { Canvas, DefaultCanvasBackground } from '@hunterchen/canvas';
// Option 1: Use the default component with custom props
<Canvas
homeCoordinates={homeCoordinates}
canvasBackground={
<DefaultCanvasBackground
gradientStyle="radial-gradient(circle, #ff6b6b 0%, #4ecdc4 100%)"
dotColor="#333333"
dotOpacity={0.5}
showFilter={false}
/>
}
>
{/* ... */}
</Canvas>
// Option 2: Pass your own custom background component
<Canvas
homeCoordinates={homeCoordinates}
canvasBackground={<MyCustomBackground />}
>
{/* ... */}
</Canvas>DefaultCanvasBackground props:
| Prop | Type | Default | Description |
|---|---|---|---|
gradientStyle |
string |
neutral gray gradient | CSS gradient string |
showDots |
boolean |
true |
Show dot pattern overlay |
dotColor |
string |
#888888 |
Dot pattern color |
dotSize |
number |
1.5 |
Dot size in pixels |
dotSpacing |
number |
22 |
Dot spacing in pixels |
dotOpacity |
number |
0.35 |
Dot pattern opacity (0-1) |
showFilter |
boolean |
true |
Show noise filter |
filterOpacity |
number |
0.6 |
Noise filter opacity (0-1) |
Customize the background shown during the intro animation:
import { Canvas, DefaultWrapperBackground } from '@hunterchen/canvas';
// Option 1: Simple gradient string
<Canvas
homeCoordinates={homeCoordinates}
introBackgroundGradient="linear-gradient(to bottom, #667eea 0%, #764ba2 100%)"
>
{/* ... */}
</Canvas>
// Option 2: Custom wrapper background component
<Canvas
homeCoordinates={homeCoordinates}
wrapperBackground={
<DefaultWrapperBackground
gradient="linear-gradient(to top, #FEB6AF 0%, #EAD2DF 50%)"
/>
}
>
{/* ... */}
</Canvas>Customize the logo and title shown during loading:
import { Canvas, DefaultIntroContent } from '@hunterchen/canvas';
<Canvas
homeCoordinates={homeCoordinates}
introContent={
<DefaultIntroContent
logoSrc="/my-logo.svg"
logoAlt="My App Logo"
logoWidth={80}
logoHeight={80}
title="MY APP"
titleClassName="text-blue-600"
/>
}
loadingText="Loading..."
>
{/* ... */}
</Canvas>Here's a complete example showing how to apply a custom theme:
import {
Canvas,
DefaultCanvasBackground,
DefaultIntroContent,
DefaultWrapperBackground,
canvasWidth,
canvasHeight,
} from '@hunterchen/canvas';
// Define your theme colors
const CANVAS_GRADIENT = `radial-gradient(ellipse ${canvasWidth}px ${canvasHeight}px at ${canvasWidth / 2}px ${canvasHeight}px, var(--coral) 0%, var(--salmon) 41%, var(--lilac) 59%, var(--beige) 90%)`;
const INTRO_GRADIENT = "linear-gradient(to top, #FEB6AF 0%, #EAD2DF 15%, #EFE3E1 50%)";
const BOX_GRADIENT = "radial-gradient(130.38% 95% at 50.03% 97.25%, #EFB8A0 0%, #EAD2DF 48.09%, #EFE3E1 100%)";
function App() {
return (
<Canvas
homeCoordinates={homeCoordinates}
navItems={navItems}
introBackgroundGradient={INTRO_GRADIENT}
canvasBoxGradient={BOX_GRADIENT}
introContent={
<DefaultIntroContent
logoSrc="/logo.svg"
logoAlt="Logo"
title="MY BRAND"
/>
}
loadingText="LOADING..."
canvasBackground={
<DefaultCanvasBackground
gradientStyle={CANVAS_GRADIENT}
dotColor="#776780"
/>
}
wrapperBackground={
<DefaultWrapperBackground gradient={INTRO_GRADIENT} />
}
>
{/* Your canvas content */}
</Canvas>
);
}The toolbar displays the current canvas coordinates and zoom level. You can customize its position, appearance, and behavior using the toolbarConfig prop.
<Canvas
homeCoordinates={homeCoordinates}
toolbarConfig={{
position: "top-right",
className: "font-sans",
style: { fontSize: "14px", color: "#525252" },
}}
>
{/* ... */}
</Canvas>ToolbarConfig options:
| Prop | Type | Default | Description |
|---|---|---|---|
hidden |
boolean |
false |
Hide the toolbar entirely |
display |
'coordinates' | 'scale' | 'both' |
'both' |
What to display |
position |
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' |
'top-left' |
Preset position |
disableAutoHide |
boolean |
false |
Disable auto-hide when at home position |
className |
string |
- | Additional CSS classes for the container |
coordinatesClassName |
string |
- | CSS classes for coordinates text |
scaleClassName |
string |
- | CSS classes for scale text |
separatorClassName |
string |
- | CSS classes for the separator |
style |
CSSProperties |
- | Inline styles for the container |
coordinatesStyle |
CSSProperties |
- | Inline styles for coordinates |
scaleStyle |
CSSProperties |
- | Inline styles for scale |
separator |
string |
'|' |
Custom separator character |
separatorGap |
number | string |
- | Gap around the separator (e.g., 8 or '0.5rem') |
coordinatesFormat |
(x: number, y: number) => string |
- | Custom coordinates formatter |
scaleFormat |
(scale: number) => string |
- | Custom scale formatter |
// Show only scale, positioned bottom-right
<Canvas
toolbarConfig={{
display: "scale",
position: "bottom-right",
}}
/>
// Custom styling with Tailwind
<Canvas
toolbarConfig={{
position: "top-right",
className: "font-sans font-medium px-4",
separatorGap: 8,
style: {
color: "#525252",
backgroundColor: "#fafafa",
borderColor: "#d4d4d4",
},
}}
/>
// Custom formatters
<Canvas
toolbarConfig={{
coordinatesFormat: (x, y) => `X: ${x} Y: ${y}`,
scaleFormat: (s) => `${(s * 100).toFixed(0)}%`,
separator: "•",
separatorGap: 12,
}}
/>
// Custom position using style
<Canvas
toolbarConfig={{
style: {
top: "50%",
left: "20px",
transform: "translateY(-50%)",
},
}}
/>
// Hide toolbar
<Canvas toolbarConfig={{ hidden: true }} />The library exports default gradient values you can use as a starting point:
import {
DEFAULT_CANVAS_GRADIENT, // Default canvas background gradient
DEFAULT_INTRO_GRADIENT, // Default intro background gradient
DEFAULT_CANVAS_BOX_GRADIENT // Default blur mask gradient
} from '@hunterchen/canvas';import {
Canvas,
CanvasProvider,
Draggable,
CanvasToolbar,
CanvasNavbar
} from '@hunterchen/canvas';
function MyCanvas() {
return (
<CanvasProvider>
<Canvas>
<CanvasToolbar />
<CanvasNavbar />
<Draggable initialX={100} initialY={100}>
<div>Drag me!</div>
</Draggable>
</Canvas>
</CanvasProvider>
);
}To build the library from source:
# Install dependencies
npm install
# Build the library
npm run build
# Run type checking
npm run type-check- Pan & Zoom: Click and drag to pan, pinch/scroll to zoom
- Draggable Elements: Built-in support for draggable components
- Performance Optimized (more to do): Adaptive rendering based on device capabilities
- Pre-compiled CSS: No Tailwind configuration needed in your project
- TypeScript Support: Full type definitions included
Canvas- Main canvas component with pan/zoom functionalityCanvasWrapper- Animated wrapper for canvas initializationCanvasComponent- Canvas component renderer with visibility optimizationDraggable,DraggableImage- Draggable elementsCanvasToolbar- Coordinate/zoom display toolbarCanvasNavbar- Navigation buttons
DefaultCanvasBackground- Customizable canvas background with gradient, dots, and filterDefaultWrapperBackground- Customizable intro/wrapper backgroundDefaultIntroContent- Customizable intro logo and titleDEFAULT_CANVAS_GRADIENT- Default canvas gradient constantDEFAULT_INTRO_GRADIENT- Default intro gradient constantDEFAULT_CANVAS_BOX_GRADIENT- Default blur mask gradient constant
CanvasProvider- Canvas state context provideruseCanvasContext- Hook to access canvas contextPerformanceProvider- Performance optimization contextusePerformanceMode,usePerformance- Performance-related hooks
useWindowDimensions- Window size trackingusePerformanceModeLegacy- Legacy performance optimization
cn- Tailwind class merging utility (usesclsx+tailwind-merge)- Canvas utility functions (pan, zoom, coordinates)
- Performance detection utilities
- Constants and types
ToolbarConfig- Toolbar customization optionsToolbarPosition- Preset toolbar positionsToolbarDisplayMode- Toolbar display modesNavItem- Navigation item configurationSectionCoordinates- Section coordinate definition
The Canvas component accepts the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
homeCoordinates |
SectionCoordinates |
Yes | - | Initial viewport position and home section |
children |
ReactNode |
Yes | - | Canvas content (typically CanvasComponent elements) |
canvasWidth |
number |
No | 6000 |
Total canvas width in pixels |
canvasHeight |
number |
No | 4000 |
Total canvas height in pixels |
navItems |
NavItem[] |
No | - | Navigation items for navbar |
skipIntro |
boolean |
No | false |
Skip intro animation |
introContent |
ReactNode |
No | - | Custom intro content during loading |
loadingText |
string |
No | - | Custom loading text |
introBackgroundGradient |
string |
No | - | Background gradient for intro |
canvasBoxGradient |
string |
No | - | Canvas box gradient during intro |
growTransition |
Transition |
No | - | Custom grow transition (Framer Motion) |
blurTransition |
Transition |
No | - | Custom blur transition (Framer Motion) |
canvasBackground |
ReactNode |
No | <DefaultCanvasBackground /> |
Custom canvas background |
wrapperBackground |
ReactNode |
No | - | Custom wrapper/intro background |
toolbarConfig |
ToolbarConfig |
No | - | Toolbar customization options |
interface DraggableProps {
initialX?: number;
initialY?: number;
children: React.ReactNode;
}Access canvas state using useCanvasContext():
const { x, y, scale } = useCanvasContext();@hunterchen/canvas/
├── dist/
│ ├── styles.css # Pre-compiled Tailwind CSS (import this!)
│ ├── index.js # Main entry point
│ ├── index.d.ts # TypeScript definitions
│ ├── components/ # Canvas components
│ ├── contexts/ # React contexts
│ ├── hooks/ # Custom hooks
│ └── lib/ # Utility functions
└── src/
├── components/
├── contexts/
├── hooks/
├── lib/
└── styles.css # Source CSS file
Make sure you've imported the CSS file:
import '@hunterchen/canvas/styles.css';Ensure you have the required peer dependencies installed:
npm install react react-dom framer-motionThe library includes full TypeScript definitions. If you're having issues, make sure your tsconfig.json includes:
{
"compilerOptions": {
"moduleResolution": "node"
}
}MIT
This library was extracted from the Hack Western 12 Website. Contributions are welcome!