# キャンバスの中のライブな HTML

`drawElement` は、本物の DOM 要素を spryte のキャンバスの中に描きます。メニュー、テキスト入力のあるフォーム、折り返しのあるリッチテキストの段落などです。要素はライブなままです。描かれた場所でクリック、フォーカス、入力を受け付け、テキストは選択でき、Ctrl+F で検索でき、スクリーンリーダーも読み上げます。ほかのスプライトと同じように、spryte の変換とシェーダーも掛かります。

ブラウザの [HTML-in-Canvas API](https://github.com/WICG/html-in-canvas) を使います。Chrome と Edge ではバージョン 157 から使えます。Firefox と Safari にはまだないので、必ずフォールバックを用意してください。

[ライブな HTML のデモ](/spryte/demos/live-html/){target="_self"}で実際に動く様子を見られます。シェーダーで浮かび上がるカードの後ろをスプライトの群れが泳ぎ、カードのテキストは選択でき、リンクとボタンも使えます。

spryte 0.9 で追加された新しい機能です。ブラウザの API が固まるまで、`drawElement` は変わる可能性があります。

```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. 最初の呼び出しで、要素をキャンバスの中へ移します（`` の子になり、`drawable` が付きます）。このときは何も描きません。
2. ブラウザが一度描画した後は、呼び出すたびに論理単位の `x, y` へ、今の変換、アルファ、シェーダーを通して描きます。
3. spryte は要素が描かれた位置をブラウザに伝えます。そのため、拡大や回転をしても、ポインタ、フォーカス、選択は描かれた位置に合います。

`drawElement` は、描いたときに `true` を返します。API がない、最初のフレームである、`draw.into()` やマスクの中で呼んだ（どちらも今は未対応）、のいずれかなら `false` を返します。

## 知っておくこと {#things-to-know}

- **位置を決めるのは CSS ではありません。** キャンバスの中では、要素のマージンや位置指定は画面上の位置になりません。`x, y` が位置を決めます。それでも CSS は残してください。API のないブラウザでは要素がページに残り、その CSS でレイアウトされます。
- **大きさは CSS の大きさです。** 何も指定しなければ、ボーダーボックスの大きさで、CSS の 1 ピクセルを 1 論理単位として描きます。拡大縮小するには `{ w, h }` を渡します。
- **変更は 1 フレーム遅れて見えます。** ブラウザが要素を描画するのはフレームの後なので、今の変更は次のフレームに現れます。
- **描き直しにはブラウザの時間がかかります。** 毎フレーム変わる要素は、毎フレーム描き直されます。変わらない要素なら、変わったときにテクスチャを 1 回送るだけです。
- **エフェクトも掛かります。** `draw.withShader()` で囲めば、ディゾルブ、波紋、CRT 風の見た目にできます。エフェクトを掛けても、テキストは選択できます。

```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()` は、描く前に、このブラウザに API があるかを返します。

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

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

## 試す {#try-it}

[サンプル](https://github.com/debuffjp/spryte/tree/main/examples/html-in-canvas)は、小さなゲーム画面の中に、テキスト入力のあるフォームを傾けて描き、その前後にスプライトを動かします。spryte のチェックアウトで `npm run example:html` を実行し、URL に `?backend=2d` を付けると Canvas 2D の経路も見られます。