Skip to content

Latest commit

 

History

54 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RageLayer

npm version CI TypeScript MIT license

Turn any web page into a destructible canvas. Smash, cut, corrode, burn, explode, undo, and combine effects across the page—then sweep everything back into place.

Try the live demo · Documentation · Tool gallery · API reference

A page after a RageLayer session

Why RageLayer?

  • It destroys the real page. The DOM is captured into a canvas, so bullets punch holes through content and fire burns text and images away.
  • Pieces become physical objects. Voronoi shards and measured DOM elements tumble, collide, and pile up through a built-in rigid-body solver.
  • Sixteen procedural tools. Seven everyday, five heavy, and four advanced tools—with no model assets or network requests, exact icon silhouettes, and configurable scale.
  • Systems that make tools interact. Four spatial combos, one consistent wood-like physical response, bounded undo/redo history, and a typed stateful custom-tool SDK.
  • A real toolbar on every stack. A complete React component, a complete Vue component, and a <rage-layer> custom element for everything else — all three rendering one shared, framework-neutral toolbar model that you can also use to build your own.
  • Operable without a mouse. Keyboard aiming puts a cursor on the page that arrow keys steer and Enter fires, so the tools themselves — not just the toolbar — are reachable from the keyboard. Every string, including tool names, can be translated.
  • Typed and extensible. Custom tools are plain TypeScript objects with access to the same rendering, physics, fire, and page-damage APIs as the built-ins.
  • Designed to degrade well. WebGL effects, page capture, audio, and physics fail or disable independently; adaptive quality keeps entity counts and rendering cost bounded, honors data saver, and suspends work in background tabs.

Install

npm install ragelayer
# pnpm add ragelayer
# bun add ragelayer

Modern ESM and TypeScript declarations are included. React, React DOM, and Vue are optional peer dependencies: install only the framework entry you use.

React / Next.js

The ready-made component mounts the engine, renders an accessible toolbar, handles keyboard shortcuts, and disposes everything when it unmounts.

import { useState } from "react";
import { RageLayer } from "ragelayer/react";

export function DestroyButton() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Destroy this page</button>
      {open && <RageLayer onClose={() => setOpen(false)} />}
    </>
  );
}

The entry is marked "use client", so it can be imported from a Next.js Client Component. Lazy load it behind the trigger if you want the normal page visit to pay no engine cost.

For a custom UI, use the headless hook:

import { useRageLayer } from "ragelayer/react";

function DestroyButton() {
  const rageLayer = useRageLayer({ initialTool: "flamethrower" });
  return <button onClick={rageLayer.toggle}>{rageLayer.isOpen ? "Repair" : "Destroy"}</button>;
}

Vue / Nuxt

The Vue component is the same ready-made toolbar as the React one:

<script setup lang="ts">
import { ref } from "vue";
import { RageLayer } from "ragelayer/vue";

const open = ref(false);
</script>

<template>
  <button @click="open = true">Destroy this page</button>
  <RageLayer v-if="open" @close="open = false" />
</template>

It renders nothing until it is mounted in a browser, so it is safe in a Nuxt page without <ClientOnly>. For a custom UI, use the headless composable, which closes the engine with its Vue effect scope:

<script setup lang="ts">
import { useRageLayer } from "ragelayer/vue";

const { isOpen, toggle } = useRageLayer({ initialTool: "hammer" });
</script>

<template>
  <button @click="toggle">{{ isOpen ? "Repair" : "Destroy this page" }}</button>
</template>

Svelte, Angular, Solid, Astro — and plain HTML

The custom element is a complete toolbar that needs no framework wrapper:

<script type="module">
  import "ragelayer/element";
</script>

<rage-layer initial-tool="hammer"></rage-layer>

It builds its UI in a shadow root, emits ragelayer-close when the visitor closes it, and disposes the engine when the element leaves the document.

Svelte's own action

To wire your own launcher instead:

<script lang="ts">
  import { rageLayer } from "ragelayer/svelte";
</script>

<button use:rageLayer={{ initialTool: "hammer" }}>Destroy this page</button>

It toggles on repeated clicks, maintains aria-pressed, and closes automatically when the button is destroyed.

Vanilla JS and every other framework

The lazy controller does no browser work until open(), which makes it safe to create during SSR:

import { createRageLayer } from "ragelayer";

const rageLayer = createRageLayer({ initialTool: "hammer" });

document.querySelector("#destroy")?.addEventListener("click", () => rageLayer.toggle());
window.addEventListener("pagehide", () => rageLayer.close());

If you already own the lifecycle, mount the engine directly:

import { mountRageLayer } from "ragelayer";

const engine = mountRageLayer({ initialTool: "blackhole", soundEnabled: true });
engine.clear();
engine.dispose();
Stack Supported API Package entry
React 18/19, Next.js Component + headless hook ragelayer/react
Vue 3, Nuxt Component + composable ragelayer/vue
Svelte, SvelteKit Custom element + action ragelayer/element, /svelte
Astro, Angular, Solid, Qwik Custom element or controller ragelayer/element
Plain JavaScript / TypeScript Controller or direct engine ragelayer
Your own toolbar UI Headless toolbar model ragelayer/toolbar

See the integration guide for SSR, cleanup and custom toolbars, and examples/ for runnable Next.js, Nuxt, SvelteKit and no-framework starters.

Custom tools

A tool is a small object. Persistent marks go on surfaceCtx; transient effects go on fxCtx.

import type { Tool } from "ragelayer";

export const stamp: Tool = {
  id: "stamp",
  name: "Stamp",
  icon: "🐾",
  hint: "click to stamp",
  onDown(engine, event) {
    engine.surfaceCtx.fillText("🐾", event.x, event.y);
    engine.shake(3);
  },
};

Pass custom tools through tools on any helper or framework adapter. The full contract is in the custom tools guide.

For isolated state, rate-limited effects, and exact custom icon bounds, use defineTool() from ragelayer/sdk. See the advanced systems guide.

Package entry points

Import Exports
ragelayer Engine, lifecycle helpers, built-in tools, types, and low-level primitives
ragelayer/engine Engine and public contracts without built-in tool models
ragelayer/tools Seven everyday tools: Hammer, Gun, Flamethrower, Water Hose, Chainsaw, Paintball, and Broom
ragelayer/tools/heavy Five heavy tools: Demolition, Rocket Launcher, Lightning, Black Hole, and Bugs
ragelayer/tools/advanced Four advanced tools: Gravity Gun, Laser Cutter, Acid Sprayer, and Sticky Bombs
ragelayer/lazy On-demand loaders for base, heavy, or complete toolsets
ragelayer/sdk Typed custom-tool factories, rate limiter, and icon metadata
ragelayer/react RageLayer, useRageLayer
ragelayer/vue RageLayer, useRageLayer
ragelayer/svelte rageLayer, createRageLayer
ragelayer/element <rage-layer>, the toolbar for every other stack
ragelayer/toolbar ToolbarModel, DEFAULT_STRINGS — build your own toolbar

The package is ESM-only, tree-shakeable, and has zero runtime dependencies — the html-to-image capture code ships inside the package as its own chunk that is loaded only when snapshot capture runs, so dist also works loaded directly in a browser without a bundler.

For the smallest deliberate setup, combine the engine-only and base-tool entries:

import { RageLayerEngine } from "ragelayer/engine";
import { baseTools } from "ragelayer/tools";

const engine = new RageLayerEngine({ toolScale: 1.15 });
engine.registerTools(baseTools);
engine.setTool("hammer");

Later, loadHeavyTools() from ragelayer/lazy can unlock the cinematic tools without putting them in the initial graph. See procedural 3D models.

Browser support

RageLayer targets current evergreen browsers and requires Canvas 2D. Chrome is covered by the runtime smoke suite. WebGL/WebGL2, WebAudio, and experimental live capture are enhancements; the engine falls back without them. Construct or open an engine only in the browser.

Page capture inherits normal browser security rules. Cross-origin images without CORS permission may prevent a complete snapshot; the engine then keeps the real page visible and uses overlay-only damage. See capture and framework integration notes.

Documentation

Development

bun install
bun run check         # types, lint, unit tests + coverage floors, build, package validation
bun run test:browser  # runtime suite in headless Chrome (real WebGL, real capture)
bun run demo:tools    # record all 16 tools as a video reel for review
bun run benchmark:low-end # fixed workloads with 6× CPU throttling
bun run docs:dev      # local documentation site
bun run docs:build    # production docs + live demo

test:browser needs a Chrome binary; point RAGELAYER_CHROME_PATH at one if it is not on the default path. It is the only place page capture, the WebGL2 surface shader and the post-processing chain actually execute, so run it before changing any of them. demo:tools writes clips, stills and an index.html to artifacts/tool-demo/ for a person to review — it is a demo, not a gate. Profiler flags and a controlled comparison workflow are documented in Performance and benchmarks.

Changes are released with Changesets. Read CONTRIBUTING.md before opening a pull request.

Credits

Inspired by the classic Windows Desktop Destroyer toy and by canvasui.dev's HTML-in-canvas work.

License

MIT © Parth Jadhav

About

Turn any web page into a destructible canvas. Smash, burn, cut, and physically demolish any page — framework-neutral, zero runtime dependencies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages