@amphore-dev/shades
v2.0.1
Published
Library for creating animated shape visualizations on canvas.
Maintainers
Readme
🎨 Shades
Library for creating animated shape visualizations on canvas.
🚀 Live Demo
View the interactive demo (deployed from the example/ directory)
Features
✨ Framework-agnostic - Works with React, Vue, Angular, Svelte, or vanilla JavaScript
🎯 TypeScript - Full type safety and IntelliSense support
🎨 Multiple shapes - Circles, squares, triangles, text, spirals, and images
🎭 Smooth animations - Buttery smooth 60fps animations
⚡ Performant - Optimized canvas rendering
🎮 Interactive - Mouse/touch controls and shades count adjustment
🐛 Debug mode - Built-in performance monitoring
Installation
npm install @amphore-dev/shades
# or
yarn add @amphore-dev/shadesQuick Start
Vanilla JavaScript/TypeScript
import { ShadesEngine } from "@amphore-dev/shades";
const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;
const shades = new ShadesEngine(canvas);
shades.generate(); // Generate shapes
shades.start(); // Start animationWith React
import { useEffect, useRef } from "react";
import { ShadesEngine } from "@amphore-dev/shades";
function ShadesComponent() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const shadesRef = useRef<ShadesEngine>();
useEffect(() => {
if (canvasRef.current) {
shadesRef.current = new ShadesEngine(canvasRef.current);
shadesRef.current.generate();
shadesRef.current.start();
}
return () => {
shadesRef.current?.destroy();
};
}, []);
return <canvas ref={canvasRef} className="w-full h-full" />;
}With Vue 3
<template>
<canvas ref="canvasRef" class="w-full h-full" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { ShadesEngine } from "@amphore-dev/shades";
const canvasRef = ref<HTMLCanvasElement>();
let shades: ShadesEngine;
onMounted(() => {
if (canvasRef.value) {
shades = new ShadesEngine(canvasRef.value);
shades.generate();
shades.start();
}
});
onUnmounted(() => {
shades?.destroy();
});
</script>API Reference
Constructor
new ShadesEngine(canvas: HTMLCanvasElement, options?: TShadesEngineOptions)Options
interface TShadesEngineOptions {
shapes?: TShadeType[]; // Exact pool of random generations (default: built-in shapes + customShapes)
customShapes?: Record<string, TShadeTypeConstructor>; // Custom shape classes (ex: { "star": StarShape })
debug?: boolean | TShadesEngineDebugOptions; // Show debug information or options
fadeDuration?: number; // Fade duration in milliseconds (default: 1000ms)
text?: string | string[]; // Text of text shapes (default: "SHADES")
font?: string; // Font family of text shapes, loaded by your page (default: "sans-serif")
intro?: TShadesPreset; // First generation, before the presets (default: none)
presets?: TShadesPreset[]; // Generations played in order
presetsEnd?: "loop" | "random"; // After the last preset (default: "random")
interactions?: TShadesEngineInteractions; // Enable, disable or override interactions
onGenerate?: (config: TShadeConfig, info: TShadesGenerationInfo) => void;
onChange?: (config: TShadeConfig) => void; // Current generation changed (wheel, pinch, set*())
autoMove?: boolean | TShadesAutoMoveOptions; // Parallax drifts on its own
autoNext?: number; // Next generation every N ms
}Methods
| Method | Description |
| ---------------------------- | --------------------------------------------------------- |
| generate() | Generate shapes and setup |
| start() | Start animation |
| pause() | Pause animation |
| next() | Play the next generation |
| destroy() | Cleanup and remove event listeners |
| setShadesCount(count) | Set the number of shades |
| adjustShadesCount(delta) | Add to the number of shades |
| setItemsCount(x, y?) | Rebuild the current grid with x × y items (y: x) |
| setSpacing(x, y?) | Gaps between the current items (y: x) |
| setColors(colors, main?) | Recolor the current items (main: random from colors) |
| setAutoMove(value) | Enable/disable/configure auto-move |
| setTargetOffset(offset?) | Move parallax target (default: center) |
| setFadeDuration(duration) | Fade duration in ms, from the next fade |
| setAutoNext(delay?) | Auto next generation every delay ms (off without value) |
| setDebug(enabled: boolean) | Toggle debug mode |
| getShadesConfig() | Get current configuration |
| isAnimating() | Check if animation is running |
Controls
- Click - Regenerate shapes
- Mouse move - Parallax effect
- Mouse wheel - Adjust number of shades
Each control can be disabled or overridden, see Interactions.
Text
text replaces the text drawn by text shapes. With an array, the intro uses
the first string and every item of a text generation picks one at random.
font must be declared by your page (@font-face); the engine loads it
before the first generation.
new ShadesEngine(canvas, {
text: ["AMPHORE", "SHADES", "HELLO"],
font: "Remained",
});Intro
intro is a preset played once, as the very first generation, before the
presets. Like a preset, any field left out is randomized. Without it, the
engine starts straight with the presets, or with random generations.
new ShadesEngine(canvas, {
text: "AMPHORE",
intro: {
type: "text",
nbrItemsX: 1,
nbrItemsY: 1,
nbrShades: 10,
width: canvas.clientWidth / 6,
colors: [{ r: 247, g: 196, b: 141 }],
},
});Presets
presets plays predefined generations in order, one per click or next()
call, after the intro. Each preset is a partial config: any field left out
is randomized (gradRatio follows nbrShades, height follows width).
Once every preset was played, presetsEnd either starts over ("loop") or
switches to random generations ("random", default).
new ShadesEngine(canvas, {
presets: [
{ type: "logo", nbrShades: 30, thickness: 1 },
{
type: "square",
nbrShades: 10,
nbrItemsX: 6,
nbrItemsY: 14,
width: 100,
spacing: 30,
},
{
type: "text",
colors: [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 200, b: 87 },
],
},
],
presetsEnd: "loop",
onGenerate: (config, { generation, intro, presetIndex }) => {
console.log(generation, intro, presetIndex, config.type);
},
});Interactions
Every interaction (click, wheel, mouseMove, touchMove, resize) is
enabled by default. Pass false to disable one, or a function to override
it. The function receives the event, the engine and the default handler,
so it can keep, wrap or replace the built-in behavior.
new ShadesEngine(canvas, {
interactions: {
wheel: false, // let the page scroll over the canvas
click: (event, engine, defaultHandler) => {
if (event.shiftKey) defaultHandler(event);
},
},
});Auto-move
autoMove makes the parallax drift on its own along a slow looping path,
like the macOS screensaver, with a random starting point for each engine.
It starts resumeDelay ms after start(). Mouse and touch moves still take
over; auto-move resumes resumeDelay ms after the last one. It stays off when the user prefers reduced motion.
Combine it with autoNext for a hands-free, screensaver-like display:
generations then only change while auto-moving, autoNext ms after
auto-move took over, so they never change under the user's pointer.
new ShadesEngine(canvas, {
autoMove: {
amplitude: 0.15, // max distance from the center, fraction of the smallest side
speed: 1, // speed multiplier
resumeDelay: 3000, // ms after the last pointer move
path: "drift", // "drift" | "circle" | "figure8" | (time) => ({ x, y })
},
autoNext: 15000, // next generation every 15s
});A custom path receives the time in seconds (multiplied by speed) and
returns a point within [-1, 1] on each axis, scaled by amplitude:
autoMove: {
path: (t) => ({ x: Math.sin(t * 0.2), y: 0 }), // horizontal sway
}Custom Shapes
Create your own custom shapes by extending the ShadeItem class:
Creating a Custom Shape
import {
ShadeItem,
TShadeColor,
TShadeConfig,
TShapeOptions,
TPoint,
} from "@amphore-dev/shades";
export class StarShape extends ShadeItem {
constructor(
x: number,
y: number,
color: TShadeColor,
options: TShapeOptions = {}
) {
super(x, y, color, { filled: true, rotation: true, ...options });
this.type = "star";
}
// Turns a bit on every draw: keeps the engine drawing every frame
isAnimated() {
return this.rotation;
}
draw = (
ctx: CanvasRenderingContext2D,
config: TShadeConfig,
offset: TPoint
) => {
const { gradRatio, nbrShades, totalWidth, totalHeight, center, width } =
config;
for (let i = 0; i < nbrShades; i++) {
this.setColors(ctx, gradRatio, i);
ctx.beginPath();
const x =
center.x +
(this.position.x - totalWidth / 2 + width / 2) +
offset.x * (1 - (gradRatio * i) / 100);
const y =
center.y +
(this.position.y - totalHeight / 2 + width / 2) +
offset.y * (1 - (gradRatio * i) / 100);
this.drawStar(ctx, x, y, width / 2, width / 4, 5);
this.drawPath(ctx);
ctx.closePath();
if (this.rotation) {
this.angle += ((0.5 / nbrShades) * Math.PI) / 180;
}
}
};
private drawStar(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
outer: number,
inner: number,
points: number
) {
const step = Math.PI / points;
let rot = (Math.PI / 2) * 3 + this.angle;
ctx.moveTo(x, y - outer);
for (let i = 0; i < points; i++) {
ctx.lineTo(x + Math.cos(rot) * outer, y + Math.sin(rot) * outer);
rot += step;
ctx.lineTo(x + Math.cos(rot) * inner, y + Math.sin(rot) * inner);
rot += step;
}
ctx.lineTo(x, y - outer);
}
}The engine skips frames where nothing changes. A shape that changes on
every draw by itself (like the rotating star above) returns true from
isAnimated(). A shape whose look changes later on its own, like an image
that finishes loading, calls this.requestRender() at that moment.
Using Custom Shapes
import { ShadesEngine } from "@amphore-dev/shades";
import { StarShape } from "./StarShape";
const engine = new ShadesEngine(canvas, {
customShapes: {
star: StarShape, // Register your custom shape
},
// Optional: the exact pool of random generations. Without it, custom
// shapes are picked along with the built-in ones
shapes: ["circle", "square", "star"],
fadeDuration: 800,
});
engine.generate();
engine.start();Benefits
- ✅ Full extensibility - Create any imaginable shape
- ✅ Seamless integration - Mix with built-in shapes
- ✅ Type safety - Complete TypeScript support
- ✅ Performance - No overhead, direct compilation
Advanced Usage
Debug Mode
import { ShadesEngine } from "@amphore-dev/shades";
// Simple debug mode
const shades = new ShadesEngine(canvas, {
debug: true,
});
// Debug with custom styling
const shadesCustom = new ShadesEngine(canvas, {
debug: {
className: "my-debug-class",
},
});Event Handling
The engine automatically handles:
- Canvas resize
- Mouse/touch events
- Animation frame management
- Memory cleanup
Custom Fade Duration
const shades = new ShadesEngine(canvas, {
fadeDuration: 2000, // 2 seconds fade
});Examples
- React Example: See
example/directory for a complete React implementation - Custom shapes: See
example/src/shapes/(StarShape,HeartShape) - Vanilla JavaScript: See
example/example-vanilla.htmlfor a pure JS example
Run them locally:
npm run example # React example (Vite dev server)
npm run example:vanilla # Vanilla example on http://localhost:8000/example/example-vanilla.htmlBuilding From Source
git clone <repository-url>
cd shades
npm install
npm run buildLicense
MIT © Amphore Dev
