HTML ใน Canvas ของ WebGL

การแสดงผล HTML ใน Canvas ของ WebGL ช่วยให้คุณใช้ซับทรี DOM แบบเรียลไทม์เป็นพื้นผิว GPU แบบไดนามิก ใช้เอฟเฟกต์ตัวปรับเฉดสีกับ DOM แบบเรียลไทม์ และผสานรวม HTML แบบอินเทอร์แอกทีฟเข้ากับฉาก 3 มิติได้

คู่มือนี้แสดงวิธีแสดงผล HTML ใน Canvas ของ WebGL ดูข้อมูลเพิ่มเติมเกี่ยวกับ HTML-in-Canvas ได้ในภาพรวม API

การแสดงผล HTML ใน Canvas ของ WebGL ต้องทำตามขั้นตอนต่อไปนี้

  1. เตรียมผืนผ้าใบ ตั้งค่าแอตทริบิวต์ที่ถูกต้องอย่างชัดเจนเพื่อให้เบราว์เซอร์ทราบว่าควรแสดงผล HTML เป็น Canvas การตั้งค่านี้เหมือนกันในบริบท 2D, WebGL และ WebGPU ดูวิธีตั้งค่า Canvas และวิธีประกาศองค์ประกอบ HTML ที่จะวาดแยกกัน
  2. ใช้ HTML เป็นพื้นผิวโดยทำดังนี้
    1. จัดสรรพื้นที่เก็บข้อมูลพื้นผิว GPU ล่วงหน้า
    2. อัปโหลดภาพรวม DOM เป็นพื้นผิว
  3. ซิงค์ DOM และพิกเซล

วาดองค์ประกอบ HTML เป็นพื้นผิว WebGL

ใน WebGL ระบบจะอัปโหลดองค์ประกอบ HTML ไปยัง GPU เป็นพื้นผิว 2 มิติโดยใช้ texElementSubImage2D()

จัดสรรพื้นที่เก็บข้อมูลพื้นผิวล่วงหน้า

บริบท WebGL ต้องมีการจัดการหน่วยความจำอย่างชัดเจน ซึ่งต่างจาก Canvas 2 มิติที่จัดการหน่วยความจำสำรองโดยอัตโนมัติ

คุณต้องจัดสรรหน่วยความจำ GPU ล่วงหน้าก่อนที่จะอัปโหลดพิกเซล DOM ซึ่งช่วยให้เบราว์เซอร์อัปเดตรูปภาพย่อยได้อย่างรวดเร็วโดยไม่ต้องเรียกใช้การจัดสรรหน่วยความจำ GPU ใหม่ซึ่งมีค่าใช้จ่ายสูง

จัดสรรบัฟเฟอร์ล่วงหน้าด้วยพารามิเตอร์ขนาดที่เลือกและตั้งค่า srcData เป็น null ดังนี้

// Only allocate texture backing once
if (!textureBufferAllocated) {
  // Pre-allocate texture backing with pre-determined size.
  gl.texImage2D(
    /* target */ gl.TEXTURE_2D,
    /* level */ 0,
    /* internalformat */ gl.RGBA8,
    /* width */ myElementWidth,
    /* height */ myElementHeight,
    /* border */ 0,
    /* format */ gl.RGBA,
    /* type */ gl.UNSIGNED_BYTE,
    /* srcData */ null
  );

  textureBufferAllocated = true;
}

หากต้องการให้พื้นผิวตรงกับขนาดเลย์เอาต์ตามธรรมชาติขององค์ประกอบ HTML ให้ใช้ canvas.getElementImageDefaultSize() เพื่อดึงขนาดเริ่มต้นในพิกเซลตารางกริดของ Canvas ดังนี้

const { width, height } = canvas.getElementImageDefaultSize(myElement);
// Pre-allocate texture backing. Only do this once, or whenever the element
// changes size.
gl.texImage2D(
  /* target */ gl.TEXTURE_2D,
  /* level */ 0,
  /* internalformat */ gl.RGBA8,
  /* width */ Math.ceil(width),
  /* height */ Math.ceil(height),
  /* border */ 0,
  /* format */ gl.RGBA,
  /* type */ gl.UNSIGNED_BYTE,
  /* srcData */ null
);

อัปโหลดภาพรวม DOM เป็นพื้นผิว

ขั้นตอนถัดไปคือการอัปโหลดองค์ประกอบ HTML ลงในพื้นผิวภายในลูปการแสดงผลหรือ Listener paint

canvas.onpaint = () => {
  // 1. Pre-allocate texture backing first
  // ...

  // 2. Upload HTML snapshot into the WebGL texture
  gl.texElementSubImage2D(
    /* target  */ gl.TEXTURE_2D,
    /* level   */ 0,
    /* xoffset */ 0,
    /* yoffset */ 0,
    /* element */ myElement // or an ElementImage from `captureElementImage()`
  );

  // ...
};

ซิงค์เรขาคณิต

เบราว์เซอร์ไม่สามารถอนุมานโดยอัตโนมัติว่าพื้นผิวจะปรากฏที่ใดบนหน้าจอในบริบท 3 มิติ ซึ่งแตกต่างจากบริบท 2 มิติ คุณต้องจัดแนวการทดสอบตัวชี้ DOM และกรอบล้อมรอบการช่วยเหลือพิเศษให้สอดคล้องกับเมช 3 มิติด้วยตนเอง

โดยสร้าง DOMMatrix ที่แมปกล่องขอบเขตขององค์ประกอบกับตำแหน่งพิกเซลของ Canvas ที่แสดงผล แล้วใช้โดยใช้ updateElementGeometry() ดังนี้

canvas.addEventListener("paint", () => {
  // 1. Upload fresh DOM snapshot to GPU texture
  gl.bindTexture(gl.TEXTURE_2D, myTexture);
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, myElement);

  // 2. Draw your 3D scene
  draw3DScene();

  // 3. Construct the CSS-pixel transform mapping the element's
  // border box to its drawn position on the canvas as needed
  // (for example: scale and translate)
  const canvasTransform = new DOMMatrix();
  // scale X
  canvasTransform.a = canvas.clientWidth / myElement.offsetWidth;
  // scale Y
  canvasTransform.d = canvas.clientHeight / myElement.offsetHeight;
  // translate X (CSS px)
  canvasTransform.e = 50;
  // translate Y (CSS px)
  canvasTransform.f = 100;

  // 4. Update DOM position, accessibility focus rings, and pointer hit-testing
  canvas.updateElementGeometry(myElement, {
    canvasTransform: canvasTransform,
  });
});

ขั้นตอนถัดไป