HTML-in-Canvas: Konsep teknis

HTML-in-Canvas API memungkinkan Anda merender subtree DOM yang interaktif dan mudah diakses langsung ke konteks kanvas 2D, WebGL, atau WebGPU.

Panduan ini menguraikan konsep teknis HTML-in-Canvas yang umum di semua konteks kanvas:

Menyiapkan kanvas untuk merender HTML

Untuk merender elemen HTML di dalam <canvas>, konfigurasikan dua atribut berikut:

  • Tetapkan content="drawable" pada <canvas>: Tindakan ini akan mengaktifkan elemen canvas untuk menata letak node DOM turunannya, bukan hanya memperlakukannya sebagai konten tersembunyi.
  • Tetapkan drawable pada elemen target: Tindakan ini menandai elemen sebagai root render independen yang memenuhi syarat untuk rendering kanvas.
<canvas id="mainCanvas" width="600" height="400" content="drawable">
  <div id="parentElement" class="card" drawable>
    <h2>Container Title</h2>
    <p>Descriptive text inside the parent element.</p>
    <!-- Nested drawable: excluded from parentElement and drawn independently -->
    <button id="childElement" class="btn" drawable>Action Button</button>
  </div>
</canvas>

Memahami subhierarki drawable

Saat menyiapkan HTML untuk dirender ke dalam kanvas, Anda harus memutuskan elemen mana yang harus digambar secara independen. Setiap elemen drawable mewakili root gambar independen dan subhierarki drawable-nya.

Sub-hierarki drawable terdiri dari elemen dengan atribut drawable dan semua turunannya, tidak termasuk turunan bertingkat yang juga membawa drawable, misalnya:

<canvas content="drawable">
  <!-- Subtree A (Renders parent container and text) -->
  <div id="parentElement" drawable>
    <h2>Parent Card Heading</h2>
    <p>Card body text content.</p>
    <!-- Subtree B (Excluded from Subtree A and drawn separately) -->
    <button id="nestedChildElement" drawable>
      Clickable Button
    </button>
  </div>
</canvas>
Elemen Batas paint Perilaku
#parentElement (Subtree A) Panel latar belakang, judul, dan paragraf Dirender saat memanggil drawElementImage(parentElement, ...)
#nestedChildElement (Subtree B) Permukaan dan teks tombol Dikecualikan dari subhierarki #parentElement; dirender dalam panggilan drawElementImage(nestedChildElement, ...) terpisah
Tombol drawable disusun bertingkat di dalam kartu drawable, dan masing-masing digambar oleh panggilan drawElementImage()-nya sendiri.

Dengan menyusun elemen drawable, Anda dapat mempertahankan hubungan HTML semantik sekaligus memberi Anda kebebasan untuk membuat lapisan, memosisikan, atau menganimasikan elemen turunan secara independen di seluruh scene kanvas.

Memahami peristiwa paint

HTML-in-Canvas API memperkenalkan peristiwa paint. Browser otomatis mengirimkan peristiwa paint di <canvas> setiap kali tata letak, teks, atau gaya CSS berubah di salah satu turunan drawable-nya.

canvas.addEventListener("paint", (event) => {
  // Clear the viewport
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Redraw elements individually
  ctx.drawElementImage(parentElement, 20, 20);
  ctx.drawElementImage(childElement, 40, 90);
});

Peristiwa paint mengirimkan instance CanvasPaintEvent, yang memperluas Event DOM standar dan memiliki properti changedElements yang hanya berisi turunan drawable yang berubah secara visual (misalnya, perubahan gaya, pergeseran tata letak, pengeditan teks, atau perubahan status fokus) sejak rendering terakhir.

canvas.addEventListener("paint", (event) => {
  // Array of changed elements
  for (const el of event.changedElements) {
    console.log(`Element #${el.id} visually changed and needs repainting`);
  }
});
Browser akan mengaktifkan paint di kanvas setiap kali rendering turunan yang dapat digambar berubah.

Siklus proses frame

Perintah gambar kanvas yang dijalankan di dalam peristiwa paint akan muncul dalam update rendering saat ini, tetapi modifikasi DOM yang dibuat di dalam peristiwa paint tidak akan muncul hingga frame berikutnya:

canvas.addEventListener("paint", () => {
  // Renders in the CURRENT rendering update:
  ctx.drawElementImage(parentElement, 20, 20);

  // Modifying DOM styles inside paint schedules a new layout cycle;
  // changes will appear in the NEXT frame:
  parentElement.style.backgroundColor = "#e0f2fe";
});

Kanvas bertingkat

Jika halaman Anda berisi elemen <canvas content="drawable"> bertingkat, browser akan mengirimkan peristiwa paint dalam urutan hierarki DOM terbalik (turunan diaktifkan sebelum induk). Hal ini memastikan elemen kanvas turunan dirender sebelum elemen kanvas induk menggabungkannya.

Misalnya, dalam contoh berikut, elemen nestedElement dalam harus berubah secara visual (misalnya, warna latar belakang CSS-nya berubah saat pengguna mengarahkan kursor ke elemen tersebut). Pertama, peristiwa paint diaktifkan di kanvas nestedCanvas. Pemrosesnya menghapus buffer turunan dan memanggil ctx.drawElementImage(nestedElement, ...) untuk merender status yang diperbarui. Kemudian, peristiwa paint diaktifkan di parentCanvas. Pemrosesnya menggambar wrapperElement (yang berisi kanvas nestedCanvas yang sudah diperbarui) ke dalam tampilan.

<!-- Outer parent canvas -->
<canvas id="parentCanvas" width="800" height="600" content="drawable">
  <!-- Wrapper container for nested content -->
  <div id="wrapperElement" drawable>

    <!-- Nested child canvas -->
    <canvas id="nestedCanvas" width="300" height="200" content="drawable">
      <div id="nestedElement" class="badge" drawable>
        Inner Child Content
      </div>
    </canvas>

  </div>
</canvas>

<script>
const parentCanvas = document.getElementById("parentCanvas");
const parentCtx = parentCanvas.getContext("2d");

const nestedCanvas = document.getElementById("nestedCanvas");
const nestedCtx = nestedCanvas.getContext("2d");

const nestedElement = document.getElementById("nestedElement");
const wrapperElement = document.getElementById("wrapperElement");

// ============================================================================
// 1. Child Canvas Listener (Fires FIRST)
// ============================================================================
nestedCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the child canvas with the updated inner element
  nestedCtx.clearRect(0, 0, nestedCanvas.width, nestedCanvas.height);
  nestedCtx.drawElementImage(nestedElement, 10, 10);

  console.log("1. Child nested canvas rendered fresh pixels.");
});

// ============================================================================
// 2. Parent Canvas Listener (Fires SECOND)
// ============================================================================
parentCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the parent canvas scene
  parentCtx.clearRect(0, 0, parentCanvas.width, parentCanvas.height);

  // Draws the wrapper container (including the freshly painted nestedCanvas)
  parentCtx.drawElementImage(wrapperElement, 50, 50);

  console.log("2. Parent canvas composited the complete scene.");
});
</script>
Saat elemen <canvas content="drawable"> disusun bertingkat, peristiwa gambar diaktifkan dalam urutan hierarki terbalik: kanvas bertingkat menggambar terlebih dahulu, sehingga kanvas induk dapat menggambar piksel terbarunya.

Memicu peristiwa paint

Panggil requestPaint() untuk menjadwalkan satu peristiwa paint pada frame berikutnya, meskipun tidak ada teks atau gaya DOM yang berubah. Hal ini dapat digunakan untuk merender adegan interaktif dengan elemen bergerak, misalnya:

let animatedX = 0;

canvas.addEventListener("paint", () => {
  animatedX += 1.5; // Move the element horizontally
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawElementImage(staticElement, 20, 20);
  ctx.drawElementImage(movingElement, animatedX, 200);
  // Request canvas paint on each rendering update for smooth animation
  canvas.requestPaint();
});

canvas.requestPaint();

Memperbarui geometri elemen

Saat Anda menggambar elemen HTML ke dalam kanvas, kode gambar Anda akan menentukan tempat elemen muncul dan ukurannya. Tidak seperti konten HTML biasa, posisi dan ukuran elemen di layar, yaitu geometrinya, tidak dapat ditentukan dari gaya yang dihitung saja. Browser memerlukan geometri elemen untuk melakukan hal berikut:

  • Mengirim peristiwa input seperti klik, ketukan, dan gerakan pointer ke elemen yang tepat. Proses ini disebut hit testing.
  • Menampilkan hasil yang akurat dari kueri tata letak, seperti getBoundingClientRect().
  • Melaporkan batas elemen ke teknologi pendukung, seperti pembesar layar.

Dalam 2D, drawElementImage() memperbarui geometri secara otomatis secara default. Argumen yang Anda teruskan ke fungsi tersebut, bersama dengan gaya yang dihitung elemen dan transformasi konteks saat ini, sudah cukup untuk menentukan tempat elemen muncul.

Dalam konteks WebGL dan WebGPU, browser tidak dapat menentukan geometri. Elemen HTML disalin ke dalam tekstur, dan program shader Anda kemudian dapat memetakan tekstur tersebut ke scene dengan cara arbitrer apa pun. Untuk memberi tahu browser tempat elemen muncul, teruskan transformasi ke updateElementGeometry().

Saat kode berhenti memanggil updateElementGeometry(), browser tidak lagi mengetahui lokasi elemen HTML pada kartu 3D. Kartu terus berputar, tetapi klik masih mengarah ke tempat semula.

Untuk memperbarui geometri, teruskan transformasi spasial ke metode. Pelajari cara membuat transformasi untuk berbagai konteks:

Mengelola urutan tumpukan hit-test

Secara default, setiap kali geometri elemen diperbarui, browser akan membawanya ke bagian atas stack interaksi (digambar terakhir = target klik paling atas). Anda dapat mengontrol perilaku ini menggunakan preserveHitTestOrder:

// Updates the backdrop without stealing clicks from foreground elements
canvas.updateElementGeometry(backgroundElement, {
  canvasTransform: customTransform,
  preserveHitTestOrder: true
});
Opsi Perilaku Kasus penggunaan
preserveHitTestOrder: false (default) Memindahkan elemen ke bagian atas stack hit-test. Elemen latar depan yang harus menerima klik terlebih dahulu.
preserveHitTestOrder: true Membiarkan elemen pada prioritas tumpukan saat ini. Elemen latar belakang yang perlu diperbarui setelah elemen latar depan.

Reset geometri

Untuk membatalkan penggambaran elemen tanpa menghapusnya dari DOM, panggil canvas.clearElementGeometry(). Tindakan ini akan mereset transformasi kanvas elemen dan menandainya sebagai di luar layar dalam hierarki aksesibilitas:

// Remove from canvas hit-testing and mark as offscreen
canvas.clearElementGeometry(childElement);

Ukuran dan unit

Saat Anda menggambar HTML ke dalam kanvas, browser beroperasi dalam tiga sistem koordinat sekaligus: piksel CSS, piksel perangkat, dan piksel petak kanvas.

Piksel CSS digunakan untuk mengukur dan memosisikan konten selama tata letak. Kemudian, browser akan menskalakannya sebesar devicePixelRatio untuk merasterisasi halaman menjadi piksel perangkat fisik. Karena piksel CSS adalah unit logis, posisi dan ukuran sering kali berakhir sebagai pecahan piksel perangkat (misalnya, 10,5 px CSS × DPR 1,5 = 15,75 px perangkat). Layar hanya dapat mewarnai seluruh piksel, sehingga browser harus me-resolve apa pun yang berada di antaranya. Hal ini dilakukan dengan dua cara:

  • Snapping: Batas kotak, batas, dan offset lapisan dibulatkan ke piksel perangkat utuh terdekat agar tetap tajam.
  • Anti-aliasing: Untuk glyph teks, kurva, dan tepi yang diubah, cakupan parsial dipadukan ke dalam piksel di sekitarnya sehingga tetap terlihat halus dan berada di tempat yang tepat.

<canvas> memiliki petak pikselnya sendiri, yang ditetapkan oleh atribut width dan height, yang terpisah dari piksel CSS dan piksel perangkat. Untuk menampilkannya, browser mengambil sampel ulang bitmap kanvas ke petak piksel fisik, dengan memadukan piksel kanvas yang berdekatan untuk mengisi setiap piksel perangkat. Untuk mempelajari cara browser memetakan antara CSS, kanvas, dan piksel perangkat, lihat Rendering yang sempurna piksel dengan devicePixelContentBox.

Setiap elemen HTML drawable ditata letak dalam piksel CSS. Karena konten yang digambar ke dalam kanvas dapat melalui dua konversi, dari piksel CSS ke petak kanvas, lalu dari petak kanvas ke piksel fisik, setiap langkah yang tidak sejajar akan menambahkan putaran penggabungan lain, dan hasilnya dapat terlihat buram. Untuk menghindari hal ini, buat petak kanvas sesuai dengan petak piksel fisik. Hal ini memiliki dua tujuan:

  • Rendering yang konsisten: Jika kanvas dipetakan 1:1 ke piksel fisik, mengonversi piksel CSS menjadi piksel kanvas akan menghasilkan hasil yang sama dengan konversi browser dari piksel CSS menjadi piksel fisik untuk HTML reguler.
  • Tidak ada penggabungan tambahan: Jika kanvas dipetakan 1:1 ke piksel fisik, mengonversinya ke piksel perangkat tidak akan menambahkan blur.

Gambar berikut menunjukkan konversi piksel yang menghasilkan konten buram. Ketiga petak tidak sejajar: Elemen kanvas berukuran 360 × 180 piksel CSS, dan ukuran kanvas adalah 300 × 150 piksel petak kanvas. Pada rasio piksel perangkat 1,5, kanvas mencakup 540 × 270 piksel perangkat. Satu piksel CSS mencakup 1,5 piksel perangkat, dan satu piksel kanvas mencakup 1,2 piksel CSS, atau 1,8 piksel perangkat (panel 1). Saat lingkaran digambar, posisi dan radiusnya berada dalam piksel kanvas, bukan piksel CSS, sehingga browser menggambarnya ke dalam bitmap kanvas 300 × 150 dan melakukan anti-alias pada tepinya (panel 2). Untuk menampilkan kanvas, browser kemudian merentangkan bitmap ini ke 540 × 270 piksel perangkat, sehingga browser harus melakukan interpolasi: setiap piksel perangkat mengambil campuran berbobot dari piksel kanvas terdekat, yang sudah berisi tepi anti-alias. Tepi tersebut dipadukan untuk kedua kalinya dan tersebar di beberapa piksel perangkat, sehingga lingkaran terlihat buram (panel 3). Hal ini dapat dihindari jika petak kanvas cocok dengan piksel perangkat 1:1. Browser kemudian akan melakukan anti-alias pada tepi hanya sekali, langsung di petak perangkat, dan menampilkan bitmap tanpa pengambilan sampel ulang, sehingga tepi tetap tajam (panel 4):

Diagram empat panel yang membandingkan petak piksel untuk tepi melengkung: 1. Grid CSS (360 x 180), kanvas (300 x 150), dan perangkat (540 x 270) yang tidak sejajar. 2. Kurva yang di-anti-alias ke petak kanvas kasar berukuran 300 x 150. 3. Bitmap tersebut diinterpolasi ke petak perangkat 540 x 270, melakukan anti-aliasing untuk kedua kalinya dan memburamkan tepi. 4. Kanvas 540 x 270 yang cocok dengan petak perangkat 1 banding 1, di-anti-alias sekali untuk tepi yang tajam.
Cara petak piksel CSS, kanvas, dan perangkat yang tidak sejajar menyebabkan anti-aliasing ganda (panel 1–3), dibandingkan dengan petak kanvas ke perangkat 1:1 (panel 4).

Untuk rendering yang jelas, petak kanvas harus sesuai dengan jumlah piksel perangkat yang dicakup kanvas: 540 × 270 dalam contoh sebelumnya.

Perbandingan per aspek kartu HTML yang bertuliskan 'Halo dari DOM! Kartu ini adalah HTML yang digambar ke dalam kanvas.' Kiri: digambar pada petak kanvas default 300 x 150, terlihat buram. Kanan: digambar pada petak kanvas berukuran 800 x 400 dengan ResizeObserver, terlihat tajam.
Kanvas yang sama, 400 x 200 piksel CSS, di layar dengan DPR 1,5. Kiri: petak kanvas 400 x 200. Kanan: disesuaikan dengan DPR, petak 600 x 300.

Menentukan ukuran petak kanvas dengan pengamat pengubahan ukuran

Ukuran petak yang tepat bergantung pada perangkat, dan dapat berubah saat tata letak berubah, saat pengguna memperbesar halaman, atau saat pengguna memindahkan jendela ke layar lain. Jadi, daripada melakukan hardcode pada ukuran petak, biarkan browser memberi tahu Anda ukuran yang seharusnya.

Agar petak kanvas tetap sinkron dengan kanvas di layar, selalu ukur dengan ResizeObserver dan sesuaikan petak kanvas ke devicePixelContentBoxSize:

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);

Mengonversi piksel CSS menjadi koordinat petak kanvas

Pengendali paint sering kali menggabungkan pengukuran DOM dengan panggilan gambar—misalnya, untuk memosisikan elemen berdasarkan ukurannya. Keduanya menggunakan unit yang berbeda:

  • Piksel kanvas mendeskripsikan petak kanvas, yang ukurannya ditentukan oleh canvas.width dan canvas.height. Misalnya, parameter dx, dy, dwidth, dan dheight dari metode gambar drawElementImage(), atau parameter API canvas lainnya, seperti fillRect() dan translate().
  • Piksel CSS mendeskripsikan halaman. Pengukuran DOM seperti offsetWidth dan clientWidth, serta koordinat peristiwa pointer seperti offsetX, menggunakan piksel CSS.

Berikut adalah ringkasan unit yang digunakan oleh berbagai parameter API:

Nilai Unit
canvas.width dan canvas.height Piksel petak kanvas
Parameter gambar 2D, termasuk dx, dy, dwidth, dan dheight dari drawElementImage() Piksel petak kanvas
width dan height ditampilkan oleh canvas.getElementImageDefaultSize() Piksel petak kanvas
Persegi panjang sumber: sx, sy, swidth, dan sheight dari drawElementImage() dan texElementSubImage2D(); sourceX, sourceY, sourceWidth, dan sourceHeight dari drawElementImageToTexture() Piksel CSS
Ukuran tekstur: Opsi width dan height dari texElementSubImage2D(); size dari drawElementImageToTexture() Piksel tekstur. Secara default, ukuran elemen dalam piksel petak kanvas, dibulatkan ke atas
Geometri elemen: getElementTransform(), serta opsi canvasTransform dan canvasClip dari updateElementGeometry() Piksel CSS
Pengukuran DOM, seperti offsetWidth, clientWidth, getBoundingClientRect(), koordinat peristiwa pointer, dan contentBoxSize entri pengamat pengubahan ukuran Piksel CSS
devicePixelContentBoxSize entri pengamat pengubahan ukuran Piksel perangkat

Jika Anda menggunakan unit yang salah, ukuran konten HTML yang digambar ke kanvas mungkin tidak sesuai dengan ekspektasi Anda. Anda perlu mengonversi nilai piksel CSS menjadi piksel petak kanvas saat menggambar HTML ke dalam kanvas. Untuk melakukannya, hitung rasio antara ukuran petak kanvas dan ukuran CSS kanvas: canvas.width / cssContentWidth secara horizontal, dan canvas.height / cssContentHeight secara vertikal. Resize observer adalah tempat yang tepat untuk menghitung rasio sebagai berikut:

// The number of canvas grid pixels per CSS pixel
let scaleX = 1;
let scaleY = 1;

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  const cssContentWidth = entry.contentRect.width;
  const cssContentHeight = entry.contentRect.height;

  // Best practice: make the canvas grid match the physical pixel grid
  if (dpc) {
    canvas.width = dpc[0].inlineSize;
    canvas.height = dpc[0].blockSize;
  } else {
    canvas.width = Math.round(cssContentWidth * window.devicePixelRatio);
    canvas.height = Math.round(cssContentHeight * window.devicePixelRatio);
  }

  scaleX = canvas.width / cssContentWidth;
  scaleY = canvas.height / cssContentHeight;
});

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

Kemudian, kalikan nilai piksel CSS dengan scaleX dan scaleY sebelum meneruskannya ke metode gambar. Misalnya, untuk menempatkan elemen drawable di tengah kanvas:

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

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

  // offsetWidth and offsetHeight are in CSS pixels.
  // Convert them to canvas grid coordinates.
  const width = card.offsetWidth * scaleX;
  const height = card.offsetHeight * scaleY;
  const x = (canvas.width - width) / 2;
  const y = (canvas.height - height) / 2;

  // drawElementImage() takes canvas grid coordinates.
  ctx.drawElementImage(card, x, y);
};

Terakhir, karena drawElementImage() menggambar kotak batas elemen, gunakan box-sizing: border-box untuk menyertakan padding dan batas dalam width dan height kartu, sehingga kartu digambar sesuai ukuran yang Anda tetapkan:

#card {
  /* Make width and height include padding and border */
  box-sizing: border-box;
  width: 200px;
  padding: 16px;
}

Memahami aksesibilitas HTML-in-Canvas

Umumnya, konten HTML <canvas content="drawable"> diekspos ke aksesibilitas dengan cara yang sama seperti DOM reguler, yang berarti konten tersebut direpresentasikan dalam hierarki aksesibilitas kecuali jika disembunyikan secara eksplisit menggunakan atribut seperti aria-hidden="true" atau inert. Namun, ada satu pengecualian utama: elemen drawable yang geometri yang dirender-nya belum ditetapkan atau diperbarui.

Elemen drawable di dalam <canvas content="drawable"> tidak memiliki geometri yang dirender jika:

  • Peta ini belum dirender dengan drawElementImage() yang memperbarui geometri secara otomatis secara default.
  • Geometrinya belum diperbarui secara eksplisit dengan updateElementGeometry().

Jika elemen drawable tidak memiliki geometri yang dirender, elemen drawable ini dan subhierarki drawable-nya akan dilaporkan ke hierarki aksesibilitas tanpa informasi geometri. Hal ini berguna, misalnya, untuk memungkinkan pembaca layar mengakses konten semantik, sekaligus memberi sinyal kepada alat bantu visual (seperti pembesar) untuk memfilter elemen yang tidak digambar.

Contoh berikut mempertahankan semantik gambar, gambar, dan teks saat menggambar gambar dan gambar secara terpisah:

<canvas id="canvas" width="400" height="300" content="drawable">
  <figure id="figure">
    <img drawable id="image" src="..." alt="image alt text" />
    <figcaption drawable id="caption">A caption</figcaption>
  </figure>
</canvas>
<script>
  const ctx = canvas.getContext('2d');

  canvas.onpaint = () => {
    ctx.reset();
    // Note: Because `preserveElementGeometry` defaults to false, the
    // `drawElementImage` call automatically updates geometry, ensuring
    // the image and caption have geometry information for accessibility.
    ctx.drawElementImage(image, 0, 0);
    ctx.drawElementImage(caption, 0, 200);
  };
</script>

Strategi penggantian

HTML-in-Canvas API dikirimkan di Chrome 157, dan mungkin tidak didukung di semua browser. Untuk memastikan solusi Anda berfungsi dengan lancar bagi semua orang, periksa apakah browser mendukung API, dan gunakan solusi alternatif jika diperlukan:

export function supportsHtmlInCanvas() {
  if (typeof document === 'undefined') return false;
  const canvas = document.createElement('canvas');
  canvas.setAttribute('content', 'drawable');
  return canvas.content === 'drawable';
}

Jika API tidak didukung, bergantung pada sasaran penerapan, Anda dapat memilih salah satu alternatif berikut:

Langkah berikutnya