A React-based canvas library for creating pannable, zoomable, and interactive canvas experiences.
npm install @hunterchen/canvas
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:
``bash`
npm install @hunterchen/canvas
This library requires the following peer dependencies:
`bash`
npm install react react-dom framer-motion
You 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 like canvas-heavy, canvas-medium, canvas-offwhite, fonts like canvas-figtree, and utilities like canvas-backface-hidden are 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):
`typescript`
import '@hunterchen/canvas/styles.css';
For Next.js:
`typescript
// pages/_app.tsx
import '@hunterchen/canvas/styles.css';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return
}
`
For Vite/React:
`typescript
// 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(
);
`
`tsx
import { Canvas, CanvasComponent } from '@hunterchen/canvas';
const homeCoordinates = { x: 0, y: 0, width: 1920, height: 1080 };
function App() {
return (
);
}
`
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:
`tsx
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
};
`
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:
`tsx
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
];
`
When navItems is provided, the canvas will render a navbar with buttons to navigate between sections.
#### Using Icons
The icon property accepts either a Lucide icon name (string) or a custom React component:
`tsx
import { Heart } from 'lucide-react';
// Using Lucide icon names (strings)
const navItems: NavItem[] = [
{ id: "home", label: "Home", icon: "Home", ...coordinates.home, isHome: true },
{ id: "about", label: "About", icon: "Info", ...coordinates.about },
];
// Using custom icon components
const CustomIcon = ({ className }: { className?: string }) => (
);
const navItems: NavItem[] = [
{ id: "home", label: "Home", icon: Heart, ...coordinates.home, isHome: true },
{ id: "custom", label: "Custom", icon: CustomIcon, ...coordinates.custom },
];
`
For Lucide icons, use the PascalCase icon name as a string (e.g., "Home", "Settings", "ChevronRight"). See the Lucide icons list for available 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:
`tsx`
homeCoordinates={homeCoordinates}
canvasWidth={8000} // Custom width (default: 6000)
canvasHeight={6000} // Custom height (default: 4000)
>
{/ ... /}
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, and CanvasComponent offsets 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.
#### Canvas Background
The canvas background consists of a gradient, dot pattern, and noise filter. Customize it by passing a canvasBackground prop:
`tsx
import { Canvas, DefaultCanvasBackground } from '@hunterchen/canvas';
// Option 1: Use the default component with custom props
homeCoordinates={homeCoordinates}
canvasBackground={
dotColor="#333333"
dotOpacity={0.5}
showFilter={false}
/>
}
>
{/ ... /}
// Option 2: Pass your own custom background component
homeCoordinates={homeCoordinates}
canvasBackground={
>
{/ ... /}
`
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) |
#### Intro/Wrapper Background
Customize the background shown during the intro animation:
`tsx
import { Canvas, DefaultWrapperBackground } from '@hunterchen/canvas';
// Option 1: Simple gradient string
homeCoordinates={homeCoordinates}
introBackgroundGradient="linear-gradient(to bottom, #667eea 0%, #764ba2 100%)"
>
{/ ... /}
// Option 2: Custom wrapper background component
homeCoordinates={homeCoordinates}
wrapperBackground={
/>
}
>
{/ ... /}
`
#### Intro Content
Customize the logo and title shown during loading:
`tsx
import { Canvas, DefaultIntroContent } from '@hunterchen/canvas';
homeCoordinates={homeCoordinates}
introContent={
logoAlt="My App Logo"
logoWidth={80}
logoHeight={80}
title="MY APP"
titleClassName="text-blue-600"
/>
}
loadingText="Loading..."
>
{/ ... /}
`
#### Complete Theming Example (Hack Western Style)
Here's a complete example showing how to apply a custom theme with warm coral/lilac colors (used by Hack Western):
`tsx
import {
Canvas,
DefaultCanvasBackground,
DefaultIntroContent,
DefaultWrapperBackground,
canvasWidth,
canvasHeight,
} from '@hunterchen/canvas';
// Hack Western theme colors (coral/lilac warm palette)
const CANVAS_GRADIENT = radial-gradient(ellipse ${canvasWidth}px ${canvasHeight}px at ${canvasWidth / 2}px ${canvasHeight}px, #f7f1e5 0%, #d9c8e6 41%, #ffb5a7 59%, #f7f1e5 90%);
const INTRO_GRADIENT = "linear-gradient(to top, #ffb5a7 0%, #d9c8e6 50%, #f7f1e5 100%)";
const BOX_GRADIENT = "radial-gradient(130.38% 95% at 50.03% 97.25%, #ffb5a7 0%, #d9c8e6 48.09%, #f7f1e5 100%)";
function App() {
return (
homeCoordinates={homeCoordinates}
navItems={navItems}
introBackgroundGradient={INTRO_GRADIENT}
canvasBoxGradient={BOX_GRADIENT}
introContent={
logoAlt="Logo"
title="HACK WESTERN"
titleClassName="text-[#513b7a]"
/>
}
loadingText="LOADING..."
canvasBackground={
dotColor="#c9a7db"
/>
}
wrapperBackground={
}
navbarConfig={{
buttonConfig: {
activeClassName: "bg-[#f5f2f7]",
hoverClassName: "bg-[#f5f2f7]",
},
}}
>
{/ Your canvas content /}
);
}
`
#### Customizing CSS Variables
The library uses CSS variables for theming. You can override them in your CSS to customize colors globally:
`css
:root {
/ Text colors /
--canvas-heavy: #3c204c; / Darkest text /
--canvas-emphasis: #513b7a; / Emphasized text /
--canvas-active: #8f57ad; / Active/selected state /
--canvas-medium: #776780; / Medium text /
--canvas-light: #c3b8cb; / Light text /
/ Background colors /
--canvas-beige: #f7f1e5; / Warm beige /
--canvas-coral: #ffb5a7; / Coral accent /
--canvas-lilac: #d9c8e6; / Lilac accent /
--canvas-salmon: #ffa585; / Salmon accent /
--canvas-tinted: #c9a7db; / Tinted purple /
--canvas-faint-lilac: #f5f2f7; / Very light lilac /
--canvas-offwhite: #fdfcfd; / Off-white /
--canvas-highlight: #f5f2f7; / Highlight/hover /
}
`
The toolbar displays the current canvas coordinates and zoom level. You can customize its position, appearance, and behavior using the toolbarConfig prop.
`tsx`
homeCoordinates={homeCoordinates}
toolbarConfig={{
position: "top-right",
className: "font-sans",
style: { fontSize: "14px", color: "#525252" },
}}
>
{/ ... /}
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 |
#### Toolbar Examples
`tsx
// Show only scale, positioned bottom-right
toolbarConfig={{
display: "scale",
position: "bottom-right",
}}
/>
// Custom styling with Tailwind
toolbarConfig={{
position: "top-right",
className: "font-sans font-medium px-4",
separatorGap: 8,
style: {
color: "#525252",
backgroundColor: "#fafafa",
borderColor: "#d4d4d4",
},
}}
/>
// Custom formatters
toolbarConfig={{
coordinatesFormat: (x, y) => X: ${x} Y: ${y},${(s * 100).toFixed(0)}%
scaleFormat: (s) => ,
separator: "•",
separatorGap: 12,
}}
/>
// Custom position using style
toolbarConfig={{
style: {
top: "50%",
left: "20px",
transform: "translateY(-50%)",
},
}}
/>
// Hide toolbar
`
The navbar provides navigation buttons to jump between canvas sections. You can customize its position, display mode, button styling, and tooltips using the navbarConfig prop.
`tsx`
homeCoordinates={homeCoordinates}
navItems={navItems}
navbarConfig={{
position: "top",
display: "icons-labels",
}}
>
{/ ... /}
NavbarConfig options:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| hidden | boolean | false | Hide the navbar entirely |display
| | 'icons' \| 'labels' \| 'icons-labels' \| 'compact' | 'icons' | Display mode for items |position
| | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | Navbar position |className
| | string | - | Additional CSS classes for container |style
| | CSSProperties | - | Inline styles for container |buttonConfig
| | NavbarButtonConfig | - | Button styling options (see below) |tooltipConfig
| | NavbarTooltipConfig | - | Tooltip options (see below) |gap
| | number | 4 | Gap between buttons in pixels |padding
| | number | 4 | Padding inside navbar in pixels |
NavbarButtonConfig options:
| Prop | Type | Description |
|------|------|-------------|
| className | string | Additional classes for all buttons |style
| | CSSProperties | Inline styles for all buttons |activeClassName
| | string | Classes for active/pushed state |activeStyle
| | CSSProperties | Styles for active state |hoverClassName
| | string | Classes for hover state |hoverStyle
| | CSSProperties | Styles for hover state |iconClassName
| | string | Classes for icons |iconSize
| | number | Icon size in pixels (default: 20) |labelClassName
| | string | Classes for labels |labelStyle
| | CSSProperties | Styles for labels |
NavbarTooltipConfig options:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| disabled | boolean | false | Disable tooltips entirely |className
| | string | - | Additional classes for tooltip |style
| | CSSProperties | - | Inline styles for tooltip |delay
| | number | 100 | Delay before showing tooltip (ms) |
#### Display Modes
- icons (default): Shows icons only, expands to show label when active, tooltip on hover
- labels: Shows labels only, no icons
- icons-labels: Always shows both icon and label for all items
- compact: Icons only, no expansion on active, just highlights
#### Navbar Examples
`tsx
// Position at top
// Labels only (no icons)
// Always show icons + labels
// Compact mode (icons only, no expansion)
// Custom container styling
navbarConfig={{
className: "bg-black/80 backdrop-blur-md",
style: { borderRadius: "20px" },
}}
/>
// Custom button styling
navbarConfig={{
buttonConfig: {
activeClassName: "bg-blue-500",
activeStyle: { color: "white" },
hoverClassName: "bg-gray-100",
iconSize: 24,
},
}}
/>
// Disable tooltips
// Vertical sidebar layout
// Hide navbar entirely
`
The library exports default gradient values you can use as a starting point:
`tsx`
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';
`tsx
import {
Canvas,
CanvasProvider,
Draggable,
CanvasToolbar,
CanvasNavbar
} from '@hunterchen/canvas';
function MyCanvas() {
return (
);
}
`
To build the library from source:
`bashInstall dependencies
npm install
Key Features
- 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
Available Exports
$3
- Canvas - Main canvas component with pan/zoom functionality
- CanvasWrapper - Animated wrapper for canvas initialization
- CanvasComponent - Canvas component renderer with visibility optimization
- Draggable, DraggableImage - Draggable elements
- CanvasToolbar - Coordinate/zoom display toolbar
- CanvasNavbar - Navigation buttons$3
- DefaultCanvasBackground - Customizable canvas background with gradient, dots, and filter
- DefaultWrapperBackground - Customizable intro/wrapper background
- DefaultIntroContent - Customizable intro logo and title
- DEFAULT_CANVAS_GRADIENT - Default canvas gradient constant
- DEFAULT_INTRO_GRADIENT - Default intro gradient constant
- DEFAULT_CANVAS_BOX_GRADIENT - Default blur mask gradient constant$3
- CanvasProvider - Canvas state context provider
- useCanvasContext - Hook to access canvas context
- PerformanceProvider - Performance optimization context
- usePerformanceMode, usePerformance - Performance-related hooks$3
- useWindowDimensions - Window size tracking
- usePerformanceModeLegacy - Legacy performance optimization$3
- cn - Tailwind class merging utility (uses clsx + tailwind-merge)
- Canvas utility functions (pan, zoom, coordinates)
- Performance detection utilities
- Constants and types$3
- ToolbarConfig - Toolbar customization options
- ToolbarPosition - Preset toolbar positions
- ToolbarDisplayMode - Toolbar display modes
- NavbarConfig - Navbar customization options
- NavbarPosition - Preset navbar positions
- NavbarDisplayMode - Navbar display modes
- NavbarButtonConfig - Navbar button styling options
- NavbarTooltipConfig - Navbar tooltip options
- NavItem - Navigation item configuration
- SectionCoordinates - Section coordinate definitionAPI Reference
$3
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 | | Custom canvas background |
| wrapperBackground | ReactNode | No | - | Custom wrapper/intro background |
| toolbarConfig | ToolbarConfig | No | - | Toolbar customization options |
| navbarConfig | NavbarConfig | No | - | Navbar customization options |$3
Each item in the
navItems array has the following properties:| Prop | Type | Required | Description |
|------|------|----------|-------------|
|
id | string | Yes | Unique identifier for this section |
| label | string | Yes | Display label shown in the navbar |
| icon | string \| React.ComponentType | Yes | Lucide icon name or custom component |
| x | number | Yes | X coordinate on the canvas |
| y | number | Yes | Y coordinate on the canvas |
| width | number | Yes | Section viewport width |
| height | number | Yes | Section viewport height |
| isHome | boolean | No | If true, clicking triggers reset/home behavior |$3
`tsx
interface DraggableProps {
initialX?: number;
initialY?: number;
children: React.ReactNode;
}
`$3
Access canvas state using
useCanvasContext():
`tsx
const { x, y, scale } = useCanvasContext();
`Library Structure
`
@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
`Troubleshooting
$3
Make sure you've imported the CSS file:
`typescript
import '@hunterchen/canvas/styles.css';
`$3
Ensure you have the required peer dependencies installed:
`bash
npm install react react-dom framer-motion
`$3
The library includes full TypeScript definitions. If you're having issues, make sure your
tsconfig.json includes:
`json
{
"compilerOptions": {
"moduleResolution": "node"
}
}
``MIT
This library was extracted from the Hack Western 12 Website. Contributions are welcome!