npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amphore-dev/shades

v2.0.1

Published

Library for creating animated shape visualizations on canvas.

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/shades

Quick 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 animation

With 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

Run them locally:

npm run example          # React example (Vite dev server)
npm run example:vanilla  # Vanilla example on http://localhost:8000/example/example-vanilla.html

Building From Source

git clone <repository-url>
cd shades
npm install
npm run build

License

MIT © Amphore Dev