# Live HTML in the canvas

`drawElement` draws a real DOM element inside your spryte canvas: a menu, a form with a text field, a paragraph of
wrapped rich text. The element stays live. It takes clicks, focus and typing where it's drawn, its text selects, Ctrl+F
finds it, and screen readers read it. It goes through spryte's transforms and shaders like any sprite.

It uses the browser's [HTML-in-Canvas API](https://github.com/WICG/html-in-canvas), which Chrome and Edge have from
version 157. Firefox and Safari don't have it yet, so always keep a fallback.

See it in the [live HTML demo](/spryte/demos/live-html/){target="_self"}: a card that dissolves in through a shader over a swarm of sprites, with text you can still select and a link and button that work.

`drawElement` is new in spryte 0.9 and may change while the browser API settles.

```ts
import { createRenderer, drawElement } from "spryte";

const draw = createRenderer(canvas);
const menu = document.getElementById("menu")!; // an ordinary element in your page

function frame() {
  draw.begin("#000", 320, 180, scale);
  drawWorld();
  if (!drawElement(draw, menu, 40, 30)) {
    // No HTML-in-Canvas here: the menu is still a normal element in the page, so there's nothing to do.
  }
  draw.end();
  requestAnimationFrame(frame);
}
```

## How it works

1. The first call moves the element into the canvas (it becomes a child of the ``, marked `drawable`) and
   draws nothing.
2. Once the browser has painted it, later calls draw it at `x, y` in your logical units, through the current
   transform, alpha and shader.
3. spryte tells the browser where the element ended up, so the pointer, focus and selection follow the drawn position,
   even scaled or rotated.

`drawElement` returns `true` when it drew, and `false` when it didn't: there's no API, it's the first frame, or you
called it inside `draw.into()` or a mask (both unsupported for now).

## Things to know

- **You place it, not CSS.** Inside the canvas, the element's margins and position don't put it on screen: `x, y` do.
  Keep the CSS anyway. Browsers without the API leave the element in the page, where that CSS lays it out.
- **Its size is its CSS size.** By default it's drawn at its border box, one CSS pixel per logical unit. Pass
  `{ w, h }` to scale it.
- **Changes show one frame late.** The browser paints the element after your frame, so a change you make now appears
  in the next frame.
- **Re-rasterising costs browser time.** An element that changes every frame is re-painted every frame. A still one
  costs one texture upload, when it changes.
- **Effects work.** Wrap it in `draw.withShader()` for a dissolve, ripple or CRT look. Its text stays selectable
  under the effect.

```ts
const dissolve = draw.createShader(`
  uniform float progress;
  vec4 effect(vec4 color, vec2 uv) {
    float n = fract(sin(dot(floor(uv * 40.0), vec2(12.9898, 78.233))) * 43758.5453);
    return source(uv) * color * step(n, progress);
  }
`);
dissolve.set("progress", Math.min(1, secondsSinceShown / 1.5));
draw.withShader(dissolve, () => drawElement(draw, card, 80, 60));
```

## Checking for support

`canDrawElements()` returns whether this browser has the API, before you draw anything:

```ts
import { canDrawElements } from "spryte";

if (!canDrawElements()) showHtmlMenu();
```

## Try it

The [example](https://github.com/debuffjp/spryte/tree/main/examples/html-in-canvas) draws a form with a text field
inside a small game view, tilted, with sprites in front and behind. Run `npm run example:html` in a checkout of spryte,
and add `?backend=2d` to the URL to see the Canvas 2D path.