メインコンテンツまでスキップ

HTMLとCSS

インターフェースは、キャンバスの中に描画しなくてもかまいません。キャンバスの上にHTMLとCSSを重ねると、インターフェースで、ブラウザのテキストレンダリングとレイアウト、テキスト入力とオートコンプリートを備えたフォームコントロール、アクセシビリティ、CSSアニメーション、あらゆるWebフォントと絵文字を利用できます。PlayCanvasアプリケーションでは、この2つを組み合わせることがよくあります。たとえば、メニューと設定画面をHTMLで作り、HUDをキャンバス内に作ります。

HTMLを使う場面​

HTMLは、メニュー、設定、フォーム、チャット、テキスト入力、長いテキスト、そしてアクセシビリティが必要なものすべてに適しています。キャンバス内UIは、HUD、シーン内やXRのインターフェース、シーンと同じフレームで描画する必要があるものやキャンバスのキャプチャに写る必要があるもの、そしてエディターで構築するインターフェースに適しています。ユーザーインターフェースのページにある比較を参照してください。

キャンバスの上にHTMLを重ねる​

キャンバスの上に、キャンバスと同じ位置とサイズのコンテナを置き、その中にHTMLを入れます。

// ウィンドウ全体を覆うオーバーレイ。キャンバスがウィンドウいっぱいに広がる場合に使う
const overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.innerHTML = `
<div class="score">Score: <span id="score">0</span></div>
<button id="pause">Pause</button>
`;
document.body.appendChild(overlay);

const style = document.createElement('style');
style.textContent = `
.overlay { position: fixed; inset: 0; pointer-events: none; font-family: sans-serif; color: white; }
.overlay .score { position: absolute; top: 16px; left: 16px; font-size: 24px; }
.overlay button { position: absolute; top: 16px; right: 16px; pointer-events: auto; }
`;
document.head.appendChild(style);

document.getElementById('pause').addEventListener('click', () => {
app.timeScale = app.timeScale === 0 ? 1 : 0;
});

オーバーレイにpointer-events: noneを指定すると、ポインターはオーバーレイの空いている部分を通り抜けてキャンバスに届きます。コントロールにpointer-events: autoを指定すると、コントロールがポインターを受け取れます。

HTMLの入力をゲームに届かないようにする​

マウスとキーボードのデバイスはページ全体をリッスンしているため、キャンバスの上にあるHTMLボタンのクリックはゲームにも届きます。app.mouse.wasPressed()はそのクリックを検出し、ボタンの下にあるキャンバス内UIも同様にクリックを受け取ります。HTMLのテキストフィールドに入力すると、そのキーがapp.keyboardに送られるため、名前を入力しているプレイヤーが前に歩いてしまうこともあります。オーバーレイでイベントを止めます。

// オーバーレイのコントロールでのクリック、ホイールの回転、キー入力はゲームに届かない
for (const type of ['mousedown', 'mouseup', 'wheel', 'keydown', 'keyup']) {
overlay.addEventListener(type, event => event.stopPropagation());
}

オーバーレイの空いている部分でのイベントは、pointer-events: noneによってキャンバスに直接送られるため、通常どおりキャンバスに届きます。

HTMLの配置​

3Dの点の上に配置する​

カメラのworldToScreen()は、キャンバスの左上隅からのCSSピクセルを返します。これはHTMLが必要とする座標です。

const onCanvas = new pc.Vec3();
app.on('update', () => {
camera.camera.worldToScreen(character.getPosition(), onCanvas);
// 点がカメラの後ろにあるときはタグを隠す
tag.style.display = onCanvas.z > 0 ? '' : 'none';
tag.style.transform = `translate(${onCanvas.x}px, ${onCanvas.y}px) translate(-50%, -100%)`;
});

これは、タグのコンテナがキャンバスを覆い、タグがその左上隅に置かれていることを前提としています。キャンバス内のエレメントで同じことを行う方法は、キャラクターの上のラベルを参照してください。エンジンのannotationsスクリプトは、この方法でモデル上にラベル付きのホットスポットを作成します。

Annotations

エレメントの上に配置する​

エレメントのcanvasCornersは、キャンバスの左上隅からのCSSピクセルで表したエレメントの四隅で、左下、右下、右上、左上の順に並びます。これを使うと、キャンバス内のエレメントのちょうど上にHTMLを配置できます。たとえば、テキストフィールドに見えるようにスタイルを付けたエレメントの上に、テキストフィールドを配置します。

function placeOver(element, html) {
const [bottomLeft, , topRight] = element.canvasCorners;
const canvasRect = app.graphicsDevice.canvas.getBoundingClientRect();
html.style.position = 'fixed';
html.style.left = `${canvasRect.left + bottomLeft.x}px`;
html.style.top = `${canvasRect.top + topRight.y}px`;
html.style.width = `${topRight.x - bottomLeft.x}px`;
html.style.height = `${bottomLeft.y - topRight.y}px`;
}

キャンバスやエレメントのサイズが変わったら、もう一度呼び出します。canvasCornersは、スクリーン空間のスクリーン上にあるエレメント用です。UIテキスト入力チュートリアルでは、この方法で完全なテキストフィールドを作成します。

パフォーマンス​

ブラウザは、キャンバスと並行して、HTMLを独自にレイアウトし描画します。テキストやスタイルを毎フレーム変更すると時間がかかり、幅や高さの変更のようにレイアウトが変わる場合には特にコストが大きくなります。キャラクターに追従するラベルのように毎フレーム動くHTMLでは、transformだけを変更します。ブラウザはページを再レイアウトせずにこれを適用できます。また、キャンバスの上にある大きな半透明のHTMLは、モバイルデバイスでは合成にも時間がかかります。

アクセシビリティ​

キャンバス内UIはただのピクセルです。スクリーンリーダーはそれを読み上げられず、キーボードで操作するユーザーはTabキーでそのコントロールに移動できません。HTMLのコントロールは、<button>、<input>、<label>などの適切な要素を使えば、デフォルトでアクセシブルです。メニュー、設定、その他アクセシビリティが必要なものにはHTMLを使い、すべてのコントロールに目に見えるフォーカス状態を持たせます。

キャンバス内のHTML​

実験的なブラウザAPIであるHTML-in-Canvasは、HTMLをテクスチャに描画します。このテクスチャは、ワールド空間のパネルなど、シーンの中で使えます。対応しているブラウザはまだ少ないため、実験的なものとして扱い、フォールバックを用意しておきます。

HTML Texture

ツール向けのPCUI​

ゲームではなくエディターやツールには、PlayCanvasエディターの構築に使われているHTMLコンポーネントライブラリ、PCUIがあります。パネル、インスペクター、ツリービュー、入力フィールドなどを、素のJavaScriptまたはReactで利用できます。

関連情報​