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

@obinexusltd/css-canvas

v0.1.0

Published

CSS Canvas Engine - render native HTML5 CSS3 components directly onto an HTML5 Canvas

Readme

@obinexusltd/css-canvas

CSS Canvas Engine — render native HTML5 / CSS3 components directly onto an HTML5 <canvas> element. Write your UI with real CSS (custom properties, flexbox, transitions, animations) and let the engine rasterize it to the canvas every frame — enabling canvas-level effects, pixel manipulation, and WebGL compositing on top of ordinary DOM markup.

npm i @obinexusltd/css-canvas

npm version ISC License GitHub


Table of Contents


How it works

┌──────────────────────────────────────┐
│  <canvas id="displayCanvas">        │  ← visible render surface
└──────────────────────────────────────┘
         ↑  ctx.drawImage each rAF
┌──────────────────────────────────────┐
│  .css-canvas-overlay  (div)          │  ← invisible DOM host
│    ├─ CSSComponent host (div)        │     opacity: 0 / pointer-events: none
│    │    └─ your HTML + CSS + JS      │     position: absolute
│    └─ …                              │     matches canvas rect
└──────────────────────────────────────┘
  1. An invisible <div> overlay is inserted next to the canvas, matching its dimensions.
  2. Your CSS/HTML components live inside the overlay — the browser handles layout, custom properties, transitions, and animations natively.
  3. On every requestAnimationFrame tick the engine serialises the overlay to an SVG <foreignObject> blob and calls ctx.drawImage, painting the CSS output onto the canvas.
  4. You can then apply WebGL effects, pixel transforms, or draw additional canvas primitives on top using engine.ctx.

Quick start

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    html, body { margin: 0; height: 100%; background: #0d0d1a; }
    #displayCanvas { display: block; width: 100vw; height: 100vh; }
  </style>
</head>
<body>
  <canvas id="displayCanvas"></canvas>

  <!-- Components live inside <template> so scripts don't execute on page load -->
  <template id="my-template">
    <div id="root">
      <style>
        .box {
          --color: #33b658;
          width: 200px; height: 200px;
          background: var(--color);
          border-radius: 12px;
          margin: auto;
          animation: spin 3s linear infinite;
        }
        @keyframes spin {
          to { transform: rotate(360deg); }
        }
      </style>
      <div class="box"></div>
    </div>
  </template>

  <script type="module">
    import { CSSCanvasEngine } from 'https://cdn.jsdelivr.net/npm/@obinexusltd/css-canvas/dist/css-canvas.esm.min.js';

    const canvas = document.getElementById('displayCanvas');
    const engine = new CSSCanvasEngine(canvas, {
      width:      window.innerWidth,
      height:     window.innerHeight,
      background: '#0d0d1a',
    });

    window.addEventListener('resize', () =>
      engine.resize(window.innerWidth, window.innerHeight)
    );

    const source = document.getElementById('my-template').content.querySelector('#root');
    await engine.mount(source, 0, 0, window.innerWidth, window.innerHeight);
    engine.start();
  </script>
</body>
</html>

Installation

npm / pnpm / yarn

npm i @obinexusltd/css-canvas

CDN (UMD, browser global CSSCanvas)

<script src="https://cdn.jsdelivr.net/npm/@obinexusltd/css-canvas/dist/css-canvas.umd.min.js"></script>
<script>
  const { CSSCanvasEngine } = CSSCanvas;
</script>

CDN (ESM)

import { CSSCanvasEngine } from 'https://cdn.jsdelivr.net/npm/@obinexusltd/css-canvas/dist/css-canvas.esm.min.js';

API reference

CSSCanvasEngine

The main entry point. Manages the canvas, the overlay, mounted components, and the render loop.

new CSSCanvasEngine(canvas, opts?)

| Parameter | Type | Default | Description | |-------------------|----------------------|------------------|-------------| | canvas | HTMLCanvasElement | required | Target render surface | | opts.width | number | canvas.width | Initial canvas width in px | | opts.height | number | canvas.height | Initial canvas height in px | | opts.background | string | 'transparent' | CSS colour painted behind every frame | | opts.fps | number | 60 | Target frames per second | | opts.debug | boolean | false | Log renderer warnings to the console |

const engine = new CSSCanvasEngine(canvas, {
  width:      800,
  height:     600,
  background: '#111',
  fps:        30,
  debug:      true,
});

engine.mount(source, x?, y?, width?, height?)Promise<CSSComponent>

Mount an HTML element as a component inside the engine overlay.

| Parameter | Type | Default | Description | |-----------|---------------------------------|-----------------|-------------| | source | Element \| DocumentFragment | required | Root element or <template>.content fragment to mount | | x | number | 0 | Left offset in canvas pixels | | y | number | 0 | Top offset in canvas pixels | | width | number | canvas.width | Component width in canvas pixels | | height | number | canvas.height | Component height in canvas pixels |

The engine clones the element's innerHTML, strips <script> tags, inserts the markup into the live overlay, then executes the scripts so that DOM APIs such as getBoundingClientRect and getComputedStyle return real values.

const template = document.getElementById('eye-template');
const source   = template.content.querySelector('#eye');

const comp = await engine.mount(source, 0, 0, window.innerWidth, window.innerHeight);
console.log('Mounted:', comp.id);

engine.unmount(id)

Remove a mounted component by its ID.

engine.unmount(comp.id);

engine.start()this

Start the rAF render loop. Safe to call multiple times.

engine.start();

engine.stop()this

Pause the render loop without destroying the engine or its components.

engine.stop();

engine.resize(width, height)this

Resize the canvas and overlay. Call this inside a window 'resize' listener.

window.addEventListener('resize', () =>
  engine.resize(window.innerWidth, window.innerHeight)
);

engine.destroy()

Stop the render loop, remove the overlay from the DOM, and clear all mounted components.

engine.destroy();

Properties

| Property | Type | Description | |---------------------|---------------------------------|-------------| | engine.canvas | HTMLCanvasElement | The render surface | | engine.ctx | CanvasRenderingContext2D | The 2D drawing context | | engine.overlay | HTMLElement | The invisible overlay div (read-only) | | engine.components | Map<string, CSSComponent> | All currently mounted components | | engine.running | boolean | Whether the render loop is active | | engine.background | string | Background fill colour |


CSSComponent

Returned by engine.mount(). Provides a handle for controlling a mounted component at runtime.

comp.show()this

Make the component visible in the next frame.

comp.hide()this

Hide the component without unmounting it.

comp.moveTo(x, y)this

Reposition the component within canvas space.

comp.moveTo(100, 200);

comp.resize(width, height)this

Resize the component's host element.

comp.resize(400, 300);

comp.setVar(name, value)this

Set a CSS custom property on the component host, cascading to all children.

comp.setVar('--eye-size', '160px');
comp.setVar('--eye-color', '#ff6b6b');

comp.getVar(name)string

Read a CSS custom property from the component's computed style.

const size = comp.getVar('--eye-size'); // '160px'

comp.query(selector)Element | null

Query for a descendant inside this component's host.

const pupil = comp.query('.pupil');

comp.queryAll(selector)NodeList

Query for all matching descendants.

const lids = comp.queryAll('.eye-lid');

comp.destroy()

Remove the component from the overlay and clean up its DOM node.

comp.destroy();

Properties

| Property | Type | Description | |---------------|---------------|-------------| | comp.id | string | Auto-generated unique ID | | comp.host | HTMLElement | Wrapper div inside the overlay | | comp.x | number | Current left offset in canvas pixels | | comp.y | number | Current top offset in canvas pixels | | comp.width | number | Current width in canvas pixels | | comp.height | number | Current height in canvas pixels | | comp.visible| boolean | Whether the component is currently visible |


CSSCanvasRenderer

Low-level rasterizer used internally by the engine. Exposed for advanced use cases — for example if you want to rasterize arbitrary DOM nodes outside the engine's render loop.

CSSCanvasRenderer.elementToImage(element, width, height)Promise<HTMLImageElement>

Static helper. Serialises a live DOM element to an <img> via an SVG <foreignObject> blob.

import { CSSCanvasRenderer } from '@obinexusltd/css-canvas';

const img = await CSSCanvasRenderer.elementToImage(
  document.querySelector('.my-component'),
  800,
  600
);
ctx.drawImage(img, 0, 0);

renderer.renderFrame(overlay, background?)Promise<void>

Clears the canvas, fills the background, and paints the overlay for one frame.


Examples

Interactive eye demo

The examples/ folder in the repository contains a full eye-tracking component demo that demonstrates:

  • CSS custom properties (--eye-size, --eye-color) as design tokens
  • mousemove interaction inside a <template> component
  • Dynamic eyelid animation via CSS transitions
  • Responsive resize handling

Run it locally:

git clone https://github.com/obinexusmk2/css-canvas.git
cd css-canvas
npx serve examples/
# open http://localhost:3000

Post-processing with engine.ctx

You can draw on top of the rasterized CSS frame each tick by listening for the next rAF and accessing engine.ctx directly:

engine.start();

function postProcess() {
  requestAnimationFrame(postProcess);
  const { ctx, canvas } = engine;

  // Invert colours over a 200×200 region
  const imageData = ctx.getImageData(0, 0, 200, 200);
  for (let i = 0; i < imageData.data.length; i += 4) {
    imageData.data[i]     = 255 - imageData.data[i];
    imageData.data[i + 1] = 255 - imageData.data[i + 1];
    imageData.data[i + 2] = 255 - imageData.data[i + 2];
  }
  ctx.putImageData(imageData, 0, 0);
}

postProcess();

Building from source

git clone https://github.com/obinexusmk2/css-canvas.git
cd css-canvas

npm install
npm run build

Output files are written to dist/:

| File | Format | Use case | |-----------------------------|--------|----------| | css-canvas.esm.js | ESM | Bundlers (Vite, Webpack, Rollup) | | css-canvas.esm.min.js | ESM | CDN ESM import, minified | | css-canvas.cjs.js | CJS | Node.js, Jest, older bundlers | | css-canvas.umd.js | UMD | Browser <script> tag (development) | | css-canvas.umd.min.js | UMD | Browser <script> tag (production) |

Watch mode:

npm run dev

Known limitations

  • External images inside components must be CORS-accessible or inlined as data: URIs. The SVG <foreignObject> serialiser cannot embed cross-origin resources.
  • Custom fonts must be declared with @font-face { src: url('data:…') }. Web font URLs are blocked by the SVG blob context.
  • CSS filters and mix-blend-mode have partial support inside <foreignObject> on some browsers — test on your target.
  • <canvas> elements inside mounted components will appear tainted in Firefox due to the SVG rasterisation path.
  • Performance — each frame serialises the full overlay to a blob and decodes it as an image. For high-frequency animations (60 fps, large canvases) consider lowering opts.fps or splitting large components into smaller ones so unchanged regions can be culled.

License

ISC © Nnamdi Michael Okpala / OBINexus


Part of the OBINexus open-source ecosystem.