Swamp Crocodile

Specimen 004 · Experiment

Floating real HTML on a canvas swamp

This site's text is drawn onto the water with the new HTML-in-Canvas API, so live, clickable HTML can bob, reflect and ripple. What the spec asks for, and what broke on the way.

Kind
Experiment
Status
Live
Surfaced
  • html-in-canvas
  • canvas
  • javascript
  • web-platform

The idea

The HTML-in-Canvas proposal lets a canvas draw its own child elements with drawElementImage(). What comes out is the browser’s real rendering of live HTML, so the text stays selectable, links stay clickable and screen readers still see it. Most demos stop at “look, a form inside a canvas”. I wanted to find out what it is actually good for, so every card on this site floats on the swamp.

What it does here

  • The headline sits at the waterline, with its reflection rippling underneath.
  • Cards bob on the surface, cast reflections and have water lapping across their lower edge.
  • When a ripple ring passes through a card, the card wobbles with it.
  • The duckweed treats the cards as solid and heaps up against their edges.

The point is not any one effect. It is that the page’s HTML now lives in the same drawing pipeline as the water, the weed and the crocodile, so it can react to them.

What the spec asks for

  • The canvas needs content="drawable", and every element you draw needs a drawable attribute, or the call throws. Earlier builds used layoutsubtree on the canvas instead. I set both.
  • Nothing can be drawn until the browser has recorded a snapshot of the element, which it announces with a paint event. Before that, the call throws No cached paint record for element.
  • Drawing HTML taints the canvas, so its pixels can never be read back.
  • In the current revision, the call moves the element’s hit testing to wherever it was drawn. In the earlier one it returned a matrix that you wrote to style.transform yourself. Both are handled.

What broke

  • The first sign never drew. My first attempt did not set drawable, so in Chrome Canary it probably failed every frame and quietly fell back to plain HTML.
  • Everything narrow got stretched. I sized the canvas bitmap from the element’s width, but the canvas itself filled its container, so anything with a max-width came out stretched sideways.
  • A hundred API calls a frame. Each reflection strip was its own drawElementImage call. Now each element is drawn once, those pixels are copied to a small scratch canvas, and the reflection and the ripple are built from the copy.
  • Five seconds of nothing. The limits for giving up were counted in frames. At 20 frames a second that meant five seconds of blank content before recovering. They are in milliseconds now.

Keeping it safe

The HTML that is served never changes. Elements move into canvases at runtime, and only where the API exists, so crawlers and every other browser get the plain page.

Two safety nets sit on top. If a browser can paint HTML into a canvas but cannot route clicks to it, the page notices, using elementFromPoint on the drawn element, and puts everything back as ordinary HTML. If the API keeps throwing, everything comes back within about a second. The sign in the footer says which of these happened.

Try it

You need Chrome Canary 149 or later with chrome://flags/#canvas-draw-element enabled, or Chrome 148 to 150 with the origin trial. Everywhere else you are reading the static version of this page, which has all of the words and none of the water effects.