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

three-html-render

v0.1.2

Published

HTML-in-Canvas polyfill and Three.js integration for rendering live HTML as WebGL/WebGPU textures

Downloads

972

Readme

three-html-render

Polyfill for the WICG HTML-in-Canvas proposal. Render live, interactive HTML as WebGL/WebGPU textures — works in all browsers today.

npm version bundle size license

Part of threepipe Part of kite3d

Features

  • HTML-in-Canvas polyfill — brings the spec to all browsers (Safari, Firefox, iOS, Android), not just Chrome Canary
  • CSS pseudo-classes:hover, :focus, :active, :focus-visible, :focus-within render correctly on 3D surfaces
  • CSS animations — spinners, transitions, keyframes render live
  • Scrolling — scrollable HTML content inside textures
  • Interaction — click buttons, type in inputs, follow links, select text on 3D meshes
  • Caret & text selection — input/textarea caret and selection highlighting rendered in the texture
  • Page-level text selection — select text across HTML elements, highlight rendered in texture
  • Native fast-path — uses texElementImage2D when available (Chrome Canary), falls back to SVG foreignObject polyfill
  • Three.js integration — automatic texture upload, DOM overlay positioning, material assignment
  • Latest Three.js support — auto-detects HTMLTexture class when available
  • Browser extension — Chrome & Safari extensions to polyfill any page

Examples

| Example | Demo | Description | |---------------------------------------------------------|----------------------------------------------------------------------------------------|-----------------------------------------------------------------------| | index.html | Live | Dragon model with scrollable HTML, hover effects, forms, theme toggle | | text-input | Live | Interactive form with caret & selection | | webGL-text-input | Live | Multi-face cube with interactive forms | | webGL | Live | Basic WebGL texture from HTML | | complex-text | Live | Rich text rendering | | pie-chart | Live | SVG/HTML chart on 3D surface | | jelly-slider | Live | WebGPU slider with copyElementImageToTexture (requires WebGPU) | | focus-ring | Live | WebGL focus glow shader with interactive form | | webxr-vr | Live | VR floating dashboards with glass panels (requires WebXR) | | webxr-ar | Live | AR panel placed on real-world surface (requires ARCore) |

Install

Polyfill

Add to page and use the html-in-canvas API normally

<script src="https://cdn.jsdelivr.net/npm/three-html-render/dist/polyfill.js"></script>

NPM

npm install three-html-render

Usage

How to render HTML inside canvas - 2d, webgl, webgpu context

Polyfill only (no Three.js)

Now any <canvas layoutsubtree> element supports the full API:

<canvas id="c" layoutsubtree>
  <div id="content" style="width:400px;height:300px">
    <h1>Hello from HTML</h1>
    <button>Click me</button>
  </div>
</canvas>
<script type="importmap">
    { "imports": {
        "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
        "three-html-render/polyfill": "https://cdn.jsdelivr.net/npm/three-html-render/dist/polyfill.mjs"
    }}
</script>
<script type="module">
  import { installHtmlInCanvasPolyfill } from 'three-html-render/polyfill'
  installHtmlInCanvasPolyfill()

  const canvas = document.getElementById('c')
  const content = document.getElementById('content')
  const ctx = canvas.getContext('2d')

  canvas.onpaint = () => {
    ctx.drawElementImage(content, 0, 0)
  }
  canvas.requestPaint()
</script>

With Three.js

<canvas id="canvas" layoutsubtree>
  <div id="htmlContent" style="width:512px;height:512px;padding:20px;background:white;font-size:24px;">
    <h1>Hello from HTML</h1>
    <button onclick="this.textContent='Clicked!'">Click me</button>
    <input type="text" value="Type here" style="font-size:20px;padding:4px;">
  </div>
</canvas>

<script type="importmap">
{ "imports": {
    "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
    "three-html-render/polyfill": "https://cdn.jsdelivr.net/npm/three-html-render/dist/polyfill.mjs",
    "three-html-render/renderer": "https://cdn.jsdelivr.net/npm/three-html-render/dist/renderer.js"
}}
</script>
<script type="module">
  import * as THREE from 'three'
  import { installHtmlInCanvasPolyfill } from 'three-html-render/polyfill'
  import { ThreeHTMLRenderer } from 'three-html-render/renderer'

  installHtmlInCanvasPolyfill()

  const canvas = document.getElementById('canvas')
  const scene = new THREE.Scene()
  const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100)
  camera.position.z = 2
  const threeRenderer = new THREE.WebGLRenderer({ canvas })
  threeRenderer.setSize(innerWidth, innerHeight)

  const geometry = new THREE.PlaneGeometry(2, 2)
  const material = new THREE.MeshBasicMaterial()
  const mesh = new THREE.Mesh(geometry, material)
  scene.add(mesh)

  const htmlRenderer = new ThreeHTMLRenderer()
  htmlRenderer.connect(canvas, camera, threeRenderer)
  htmlRenderer.addObject(document.getElementById('htmlContent'), mesh)

  function animate() {
    requestAnimationFrame(animate)
    htmlRenderer.update()
    threeRenderer.render(scene, camera)
  }
  animate()
</script>

The HTML element should be a child of a <canvas layoutsubtree>. The renderer handles texture upload, DOM overlay positioning, event propagation, and material assignment automatically. Works with Three.js >= 0.150.0.

What Gets Polyfilled

The polyfill implements the full WICG HTML-in-Canvas API surface:

| API | Target | Description | |-----------------------------------------------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------| | layoutsubtree | HTMLCanvasElement | Attribute that opts canvas children into layout | | onpaint | HTMLCanvasElement | Event fired when children need re-rendering | | requestPaint() | HTMLCanvasElement | Request a paint event on the next frame | | captureElementImage() | HTMLCanvasElement | Capture a child element's rendered snapshot | | getElementTransform(element, drawTransform) | HTMLCanvasElement | Get CSS transform to align DOM overlay with drawn position. drawTransform is the DOMMatrix returned by drawElementImage. | | drawElementImage() | CanvasRenderingContext2D | Draw a child element onto the 2D canvas. Returns a DOMMatrix. | | texElementImage2D() | WebGLRenderingContext | Upload a child element as a WebGL texture | | copyElementImageToTexture() | GPUQueue | Upload a child element as a WebGPU texture |

API

installHtmlInCanvasPolyfill(options?)

Installs the polyfill on all <canvas layoutsubtree> elements.

| Option | Type | Default | Description | |--------------|-----------|---------|--------------------------------------------------------------------------------------------| | force | boolean | false | Install even if native API is available. Also activates when ?polyfillHIC is in the URL. | | pageStyles | string | — | Additional CSS to include in renders |

uninstallHtmlInCanvasPolyfill()

Cleanly removes the polyfill, restoring all patched prototypes and tearing down canvas states.

getHtmlRenderer()

Returns the internal HtmlRenderer instance used by the polyfill. Useful for advanced operations like invalidating cached styles:

import { getHtmlRenderer } from 'three-html-render/polyfill'
getHtmlRenderer().invalidatePageStylesCss()

ThreeHTMLRenderer

Connects HTML elements to Three.js meshes for texture rendering and DOM overlay interaction.

Methods

| Method | Description | |-------------------------------------|-----------------------------------------------------------------------| | connect(canvas, camera, renderer) | Bind to a Three.js canvas, camera, and WebGL renderer | | addObject(element, mesh) | Register an HTML element to render onto a mesh | | update() | Call every frame — positions DOM overlay and triggers texture updates | | getTexture(element) | Get the Three.js Texture for a given element |

Properties

| Property | Type | Default | Description | |--------------------|-----------------------|---------|-------------------------------------------| | selectionOpacity | number | 0 | DOM overlay opacity when text is selected | | overlayRenderer | HtmlOverlayRenderer | — | The underlying overlay positioning engine |

Texture Upload Strategy

ThreeHTMLRenderer automatically picks the best upload path:

  1. Latest Three.js (HTMLTexture available) — renderer handles everything
  2. Native Canary (texElementImage2D on GL context) — direct GL upload
  3. PolyfillcaptureElementImage → canvas → texture.image

How It Works

  1. The polyfill moves <canvas> children into an offscreen host div, rasterizes them via SVG foreignObject → <img> → canvas
  2. CSS pseudo-classes (:hover, :focus, :active, etc.) are rewritten to real CSS classes (.pseudo-hover, .pseudo-focus, .pseudo-active) and injected into the SVG stylesheet. Mouse/focus/pointer events toggle these classes on the host overlay.
  3. Input caret and text selection are measured from the live DOM and injected as positioned <div> elements into the SVG clone
  4. onpaint / requestPaint API lets consumers control when rasterization happens
  5. ThreeHTMLRenderer positions a transparent DOM overlay (using matrix3d math) so the browser handles hit-testing natively
  6. Texture is uploaded to WebGL/WebGPU each frame via the best available path

Run npm run dev to start the dev server locally.

Browser Support

| Browser | Support | Method | |------------------------|-------------------|-------------------------------------------------------| | Chrome, Edge | Full | Polyfill (SVG foreignObject) | | Safari, iOS Safari | Full | Polyfill | | Firefox | Full | Polyfill | | Android Chrome/WebView | Full | Polyfill | | Chrome Canary | Native + Polyfill | Native via chrome://flags/#canvas-draw-element flag |

The HTML-in-Canvas API is a WICG proposal currently in developer trial in Chrome Canary, with an origin trial planned for Chrome M148-M151. The polyfill ensures your code works today and will automatically use the native fast-path when browsers ship support.

Known Limitations

  • Textarea internal scroll is not reflected in the texture (content renders at scroll position 0)
  • contenteditable elements don't support caret/selection rendering
  • Dynamic stylesheets added after polyfill installation need getHtmlRenderer().invalidatePageStylesCss() to pick up new pseudo-class rules
  • :visited pseudo-class cannot be polyfilled (browser privacy restriction)
  • Some CSS features may render differently in SVG foreignObject context (e.g., form control appearance, color-scheme)

Integration

This library works with vanilla Three.js (>= 0.150.0). The functionality is also built into threepipe (GitHub) and kite3d as plugins — manual code integration is not required when using those frameworks.

If you are using React Three Fiber or another Three.js framework, refer to their documentation for integration guidance.

Browser Extension

Chrome and Safari extensions are included to polyfill any page. See extension/README.md for build and installation instructions.

Development

npm run dev              # Start Vite dev server
npm run build            # Build library (ESM + IIFE + .d.ts)
npm run build:demo       # Build demo site (for GitHub Pages)
npm run build:extension  # Build browser extension
npm run typecheck        # Run TypeScript type checking

License

MIT

Contributing

Contributions welcome! See CONTRIBUTING.md for development setup and guidelines.