Memperkenalkan uji coba origin HTML-in-Canvas API

Thomas Nattestad
Thomas Nattestad

Selama bertahun-tahun, developer web harus membuat pilihan arsitektur yang sulit saat membangun aplikasi visual yang kompleks dan sangat interaktif di web: apakah Anda mengandalkan DOM untuk fitur semantiknya yang kaya, atau merender langsung ke elemen <canvas> untuk performa grafis tingkat rendah?

Dengan HTML-in-Canvas API eksperimental baru—yang kini tersedia dalam uji coba origin—Anda tidak perlu memilih. API ini memungkinkan Anda menggambar konten DOM langsung ke kanvas 2D atau tekstur WebGL/WebGPU sambil menjaga UI tetap dapat berinteraksi, dapat diakses, dan terhubung ke fitur browser favorit Anda. Dengan menggabungkan HTML dengan pemrosesan grafis tingkat rendah, Anda dapat menciptakan pengalaman yang sebelumnya tidak mungkin dilakukan.

DOM versus Canvas

Untuk memahami keunggulan API baru ini, Anda perlu melihat kekuatan relatif DOM dan Canvas.

DOM adalah elemen utama UI web. Compose menawarkan solusi tata letak teks langsung, menggunakan konten yang dipahami secara semantik untuk membuat antarmuka yang kaya. Hal ini memungkinkan pengguna melakukan operasi umum di seluruh halaman web dengan lancar—hal-hal yang sering kita anggap remeh, seperti menandai teks untuk disalin, atau mengklik kanan gambar untuk menyimpannya. DOM juga terintegrasi dengan fitur browser penting: alat aksesibilitas, terjemahan, temukan di halaman, mode pembaca, ekstensi, mode gelap, zoom browser, dan isi otomatis.

Canvas (dan WebGL/WebGPU), di sisi lain, memungkinkan akses tingkat rendah untuk mendorong kisi piksel untuk grafik 2D dan 3D yang sangat canggih. Game dan aplikasi web yang kompleks (seperti Google Dokumen atau Figma) memerlukan akses tingkat rendah yang berperforma tinggi ini. Karena kanvas pada dasarnya adalah petak piksel, dukungan untuk fitur seperti teks responsif memerlukan logika UI kustom yang kompleks, sehingga meningkatkan ukuran paket Anda secara drastis. Yang penting, semua fitur browser canggih yang terintegrasi ke dalam DOM akan rusak sepenuhnya saat UI terjebak di dalam petak piksel kanvas statis.

Keuntungan memindahkan DOM ke Canvas

HTML-in-Canvas API adalah jembatan yang memberi Anda yang terbaik dari kedua dunia. Dengan menempatkan HTML di dalam elemen <canvas> dan menyinkronkan transformasinya, Anda memastikan konten tetap sepenuhnya interaktif, dan semua integrasi browser berfungsi secara otomatis.

Berikut yang akan Anda dapatkan dengan membiarkan DOM menangani UI Anda di dalam elemen <canvas>:

  • Tata letak dan pemformatan teks: Tata letak dan pemformatan teks yang disederhanakan, termasuk teks multiline atau dua arah dengan gaya CSS yang diterapkan.
  • Kontrol formulir: Kontrol formulir yang ekspresif dan lebih mudah digunakan dengan opsi penyesuaian yang ekstensif.
  • Seleksi teks, salin/tempel, dan klik kanan: Pengguna dapat menandai teks di dalam adegan 3D Anda, atau mengklik kanan menu konteks secara native.
  • Seleksi teks, salin/tempel, dan klik kanan: Pengguna dapat menandai teks di dalam adegan 3D Anda, atau mengklik kanan menu konteks secara native.
  • Aksesibilitas: Konten yang dirender di dalam kanvas diekspos ke hierarki aksesibilitas. Sistem aksesibilitas dapat mengurai UI seperti yang dilakukan HTML normal, dan mengeksposnya ke sistem seperti pembaca layar.
  • Find-in-page: Pengguna dapat menggunakan fitur cari di halaman (Ctrl/Cmd+F) untuk menelusuri teks, dan browser akan menandainya langsung dalam tekstur WebGL Anda.
  • Find-in-page: Pengguna dapat menggunakan fitur cari di halaman (Ctrl/Cmd+F) untuk menelusuri teks, dan browser akan menandainya langsung dalam tekstur WebGL Anda.
  • Dapat diindeks dan dapat berinteraksi dengan agen AI: Peng-crawl web dan agen AI dapat mengindeks dan membaca teks yang dirender ke dalam adegan 2D dan 3D Anda dengan lancar.
  • Integrasi ekstensi: Ekstensi browser berfungsi secara native. Misalnya, ekstensi penggantian teks akan otomatis memperbarui teks yang dirender pada mesh 3D Anda.
  • Integrasi DevTools: Anda dapat memeriksa konten kanvas, termasuk elemen UI WebGL/WebGPU langsung di Chrome DevTools. Sesuaikan gaya CSS di pemeriksa, dan lihat perubahannya langsung di tekstur 3D.

Kasus penggunaan tingkat tinggi

API ini membuka potensi luar biasa di beberapa domain:

  • Aplikasi berbasis kanvas besar: Aplikasi web berat seperti Google Dokumen, Miro, atau Figma kini dapat merender komponen UI aplikasi yang kompleks secara native ke dalam ruang kerja berbasis kanvasnya, sehingga meningkatkan aksesibilitas dan mengurangi ukuran paket.
  • Game dan adegan 3D: Situs pemasaran, pengalaman WebXR imersif, dan game web kini dapat menempatkan UI web yang sepenuhnya interaktif ke dalam adegan 3D—seperti buku 3D yang menggunakan teks DOM asli, atau terminal dalam game yang secara native mendukung penyalinan dan penempelan.

Cara menggunakan API

Penggunaan API terjadi dalam tiga fase: Menyiapkan kanvas, merender ke dalam kanvas, dan memperbarui transformasi CSS sehingga browser mengetahui lokasi fisik elemen di layar.

Prasyarat

HTML-in-Canvas API berada dalam uji coba origin di Chrome 148 hingga 150. Untuk mengujinya di situs Anda, gunakan Chrome Canary 149 atau yang lebih baru dengan tanda chrome://flags/#canvas-draw-element diaktifkan. Untuk mengaktifkan API bagi pengguna lain, daftar ke Uji Coba Origin.

Langkah 1: Penyiapan Canvas dasar

Pertama, tambahkan atribut layoutsubtree ke tag <canvas> Anda. Tindakan ini membuat browser mengetahui konten yang berada di dalam kanvas, mempersiapkannya untuk ditampilkan di dalam kanvas, dan mengeksposnya ke hierarki aksesibilitas.

<canvas id="canvas" style="width: 200px; height: 200px;" layoutsubtree>
  <div id="form_element">
    <label for="name">Name:</label> <input id="name" type="text">
  </div>
</canvas>

Mengatur ukuran petak kanvas

Untuk menghindari konten yang dirender terlihat buram, pastikan untuk menyesuaikan ukuran petak kanvas agar sesuai dengan faktor skala perangkat.

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
  canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
});

const supportsDevicePixelContentBox =
  typeof ResizeObserverEntry !== 'undefined' &&
  'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);

Langkah 2: Rendering

Untuk konteks 2D, gunakan metode drawElementImage. Lakukan ini di dalam peristiwa paint, yang dipicu setiap kali elemen digambar ulang—misalnya, selama penyorotan teks atau input pengguna. Penting untuk memperbarui transformasi CSS elemen dengan nilai yang ditampilkan agar interaktivitas terus berfungsi.

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();

  // Draw the form element at x:0, y:0
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // Use the transform returned later on...
};

Merender dengan WebGL

Untuk WebGL, Anda menggunakan texElementImage2D. Fungsinya mirip dengan texImage2D, tetapi menggunakan elemen DOM sebagai sumber.

canvas.onpaint = () => {
  if (gl.texElementImage2D) {
    gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
  }
};

Merender dengan WebGPU

WebGPU menggunakan metode copyElementImageToTexture pada antrean perangkat, yang serupa dengan copyExternalImageToTexture:

canvas.onpaint = () => {
  root.device.queue.copyElementImageToTexture(
    valueElement,
    { texture: targetTexture }
  );
};

Langkah 3: Perbarui transformasi CSS

Setelah merender elemen ke dalam kanvas, Anda harus memperbarui browser tentang lokasinya. Hal ini memastikan sinkronisasi spasial antara tata letak kanvas dan DOM. Hal ini penting agar browser dapat memetakan zona peristiwa dengan benar—seperti di mana tepatnya pengguna mengklik atau mengarahkan kursor—dengan tempat elemen dirender.

Untuk kasus konteks 2D, terapkan transformasi yang ditampilkan oleh panggilan rendering ke .style.transform property:

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();
  // Draw the form element at x:0, y:0
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // Sync the DOM location with the drawn location
  form_element.style.transform = transform.toString();
};

Dengan WebGL atau WebGPU, lokasi elemen di layar bergantung pada cara tekstur output digunakan oleh kode shader, dan tidak dapat disimpulkan dari konteks rendering kanvas. Namun, jika program shader Anda menggunakan proyeksi tampilan model umum untuk menggambar tekstur, Anda dapat menggunakan fungsi praktis baru element.getElementTransform() untuk menghitung transformasi yang dapat digunakan dengan cara yang sama seperti nilai yang ditampilkan dari drawElementImage(). Untuk memfasilitasi hal ini, Anda perlu melakukan hal berikut:

  • Konversi Matriks MVP WebGL ke Matriks DOM.
  • Menormalisasi elemen HTML. Elemen HTML berukuran dalam piksel (misalnya, lebar 200 piksel). Namun, WebGL biasanya memperlakukan objek sebagai "kotak satuan", misalnya, mulai dari 0 hingga 1. Jika Anda tidak menormalisasi, tombol 200 px akan terlihat 200 kali lebih besar.
  • Memetakan ke area pandang kanvas. Langkah ini adalah fase "menskalakan ulang": langkah ini meregangkan kembali matematika ruang satuan agar sesuai dengan dimensi piksel sebenarnya dari elemen <canvas> Anda di layar. Hal ini juga membalikkan sumbu Y, karena di WebGL, ke atas adalah positif, tetapi di CSS, ke bawah adalah positif.
  • Hitung transformasi akhir. Kalikan matriks secara berurutan: Viewport * MVP * Normalization. Menggabungkannya menjadi satu transformasi akhir akan menghasilkan "peta" yang memberi tahu browser secara persis di mana lapisan elemen HTML tersebut harus ditempatkan agar sejajar dengan gambar 3D.
  • Terapkan transformasi ke elemen HTML. Hal ini memindahkan lapisan elemen HTML agar berada tepat di atas piksel yang dirender. Hal ini memastikan bahwa saat pengguna mengklik tombol atau memilih teks, mereka akan menekan elemen HTML yang sebenarnya.
if (canvas.getElementTransform) {
  // 1. Convert WebGL MVP Matrix to DOM Matrix
  const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));

  // 2. Normalize the HTML element (pixels -> 1x1 unit square)
  const width = targetHTMLElement.offsetWidth;
  const height = targetHTMLElement.offsetHeight;

  const cssToUnitSpace = new DOMMatrix()
    .scale(1 / width, -1 / height, 1) // Shrink to unit size and flip Y
    .translate(-width / 2, -height / 2); // Center the element

  // 3. Map to the canvas viewport
  const clipToCanvasViewport = new DOMMatrix()
    .translate(canvas.width / 2, canvas.height / 2) // Move origin to center
    .scale(canvas.width / 2, -canvas.height / 2, 1); // Stretch to canvas dimensions

  // 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
  const screenSpaceTransform = clipToCanvasViewport
      .multiply(mvpDOM)
      .multiply(cssToUnitSpace);

  // 5. Apply to the transform
  const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
  if (computedTransform) {
    targetHTMLElement.style.transform = computedTransform.toString();
  }
}

Dukungan library dan framework

Beberapa library populer telah menyediakan dukungan untuk fitur HTML di Canvas.

Three.js

Memperbarui matriks secara manual bisa membosankan, itulah sebabnya framework sudah mulai menggunakannya. Three.js memiliki dukungan eksperimental menggunakan THREE.HTMLTexture baru:

const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // Pass the DOM element

const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

PlayCanvas

PlayCanvas juga mendukung HTML-in-Canvas menggunakan Texture API mereka:

// Wait for the 'paint' event to set the source
canvas.addEventListener('paint', () => {
    htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();

// Keep up to date
canvas.addEventListener('paint', onPaintUpload);

const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();

Demo

Sebelum mencoba demo, pastikan lingkungan Anda dikonfigurasi dengan benar.

Ada beberapa demo yang berfungsi sebagai referensi untuk menggunakan API. Kami telah melihat solusi kreatif dari komunitas, mulai dari buku 3D yang dapat diterjemahkan hingga elemen UI yang dibiaskan melalui shader kaca:

  • Buku 3D: Buku 3D yang dirender WebGL yang menggunakan tata letak HTML untuk halamannya. Pengguna dapat menukar font dengan CSS. Karena berbasis DOM, terjemahan bawaan berfungsi secara instan, dan agen AI dapat mengekstrak teks dengan lebih mudah.
  • UI 3D interaktif: Penggeser jelly WebGPU yang membiaskan cahaya berdasarkan model 3D yang mendasarinya, sekaligus merespons atribut langkah <input type="range"> HTML standar.
  • Tekstur animasi: Billboard 3D dinamis yang merender pensil SVG animasi menggunakan DOM langsung ke tekstur WebGL tanpa memerlukan loop animasi kustom.
  • Overlay refraktif: Lapisan tipografi interaktif yang terdistorsi oleh kursor 3D yang bergerak, tetapi dapat dipilih dan ditelusuri sepenuhnya menggunakan fitur temukan di halaman.

Lihat kumpulan demo yang dibuat oleh komunitas. Jika Anda ingin demo HTML-in-Canvas ditampilkan dalam koleksi ini, buat permintaan penarikan untuk menambahkannya.

Batasan

Meskipun sangat efektif, API ini memiliki beberapa batasan yang disadari:

  • Konten lintas origin: Untuk alasan keamanan dan privasi, API tidak berfungsi dengan konten iframe lintas origin.
  • Scrolling thread utama: HTML dalam kanvas digambar dengan JavaScript, yang berarti bahwa scrolling dan animasi tidak dapat diperbarui secara independen dari JavaScript, seperti yang dapat dilakukan di luar kanvas. Developer harus mempertimbangkan dengan cermat karakteristik performa dalam menempatkan konten yang dapat di-scroll di dalam kanvas dibandingkan dengan membuat seluruh kanvas dapat di-scroll.

Masukan

Jika Anda bereksperimen dengan HTML-in-Canvas API, kami ingin mendengar pendapat Anda. Anda dapat mendaftar ke uji coba origin untuk mengaktifkan fitur di situs Anda selama fitur tersebut dalam tahap eksperimental untuk membantu kami membentuk desain API. Anda juga dapat mengajukan masalah untuk memberikan masukan.

Resource