แสดงเนื้อหา Canvas ต่อฟีเจอร์ของเบราว์เซอร์

ฟีเจอร์บนเว็บ: HTML ใน Canvas

เนื้อหา <canvas> ปกติจะไม่แสดงต่อฟีเจอร์ของเบราว์เซอร์ เช่น โปรแกรมอ่านหน้าจอ การจัดทำดัชนี เครื่องมือแปลภาษา เครื่องมือช่วยเหลือด้านการช่วยเหลือพิเศษ การค้นหาในหน้าเว็บ การพิมพ์ ฯลฯ แต่เมื่อใช้ HTML in canvas คุณจะแสดงผล DOM จริงในองค์ประกอบ Canvas ได้โดยตรง การเพิ่มแอตทริบิวต์ content="drawable" ลงในองค์ประกอบ HTML <canvas> จะจัดวางองค์ประกอบ HTML ที่สืบทอดมา และแอตทริบิวต์ drawable จะทำเครื่องหมายองค์ประกอบที่คุณแสดงผลได้ภายในบริบทการแสดงผลของ Canvas คุณสามารถใช้เพื่อจัดรูปแบบและจัดวางข้อความใน Canvas, แสดงเนื้อหา Canvas ต่อฟีเจอร์ของเบราว์เซอร์ (เช่น การช่วยเหลือพิเศษ การแปล หรือการค้นหาในหน้า) และใช้เอฟเฟกต์ 2 มิติและ 3 มิติกับ HTML

วิธีการนำไปใช้งาน

  1. ตรวจสอบว่าเบราว์เซอร์รองรับ HTML-in-Canvas หรือไม่ โดยทำดังนี้

    if ('content' in HTMLCanvasElement.prototype) {
      // Use HTML in Canvas API
    } else {
      // Use fallback strategy
    }
    
  2. เพิ่มแอตทริบิวต์ content="drawable" ลงในองค์ประกอบ HTML <canvas>

  3. วางเนื้อหา HTML ไว้ภายในองค์ประกอบ <canvas> และเพิ่มแอตทริบิวต์ drawable ลงในทุกองค์ประกอบที่คุณวาด องค์ประกอบที่มี drawable จะจับภาพแผนผังย่อยของตนเอง ยกเว้นองค์ประกอบสืบทอดที่ซ้อนกันซึ่งเป็น drawable ด้วย

    <canvas id="canvas" content="drawable">
      <div id="html-content" drawable></div>
    </canvas>
    
  4. ปรับขนาดตารางกริดของ Canvas ให้ตรงกับค่าตัวคูณมาตราส่วนของอุปกรณ์เพื่อป้องกันไม่ให้ภาพเบลอ โดยทำดังนี้

    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);
    
  5. แสดงผลเนื้อหา HTML ไปยัง Canvas ภายในเครื่องจัดการเหตุการณ์ canvas.onpaint องค์ประกอบจะวาดได้หลังจากที่ Canvas แสดงผลแล้วเท่านั้น โดยเรียกใช้ canvas.requestPaint() เพื่อขอเหตุการณ์ paint

  • ในบริบท 2 มิติ ให้ใช้เมธอด drawElementImage โดยจะวาดภาพรวมล่าสุดขององค์ประกอบ drawable และแผนผังย่อยที่วาดได้ โดยเริ่มที่กล่องเส้นขอบขององค์ประกอบก่อนการเปลี่ยนรูปแบบ CSS ดังนี้
canvas.onpaint = () => {
  ctx.reset();
  // Draw the form element at x:0, y:0
  ctx.drawElementImage(form_element, 0, 0);
};
  • ในบริบท WebGL และ WebGPU คุณจะคัดลอกองค์ประกอบลงในพื้นผิว ปรับขนาดพื้นผิวจาก canvas.captureElementImage(element) width และ height อยู่ในพิกเซลตารางกริดของ Canvas (ที่เก็บข้อมูลสำรอง) ดังนั้นให้ปัดเศษขึ้นด้วย Math.ceil() captureElementImage() จะแสดงข้อผิดพลาดหากองค์ประกอบยังไม่มีบันทึกการแสดงผล: เรียกใช้ requestPaint() แล้วลองอีกครั้งในเหตุการณ์ paint ถัดไป
// Returns the element's size in canvas grid pixels, or null if the element
// has no paint record yet.
function getElementImageSize(canvas, element) {
  let elementImage;
  try {
    elementImage = canvas.captureElementImage(element);
  } catch (err) {
    canvas.requestPaint(); // No paint record yet: retry on the next paint event.
    return null;
  }
  const width = Math.ceil(elementImage.width);
  const height = Math.ceil(elementImage.height);
  elementImage.close();
  return width > 0 && height > 0 ? { width, height } : null;
}
  • ในบริบท WebGL ให้จัดสรรการสนับสนุนพื้นผิวก่อนโดยใช้เมธอด texImage2D ข้อมูลโค้ดในคู่มือนี้ใช้บริบท WebGL 2 (canvas.getContext("webgl2")) จัดสรรพื้นที่เก็บข้อมูลพื้นผิวโดยเรียกใช้ texImage2D() ด้วย null แทนข้อมูลพิกเซล ซึ่งจะจองหน่วยความจำ GPU ตามขนาดและรูปแบบที่ระบุโดยไม่ต้องอัปโหลดพิกเซลใดๆ โดยทำตามกฎต่อไปนี้
    • ปรับขนาดพื้นผิวจาก captureElementImage() ดังที่แสดงด้านบน
    • จัดสรรระดับ 0 เฉพาะในการอัปโหลดครั้งแรก หรือเมื่อขนาดมีการเปลี่ยนแปลง เนื่องจากการจัดสรรใหม่จะล้างพื้นผิว ใช้พื้นที่เก็บข้อมูลที่เปลี่ยนแปลงได้: พื้นที่เก็บข้อมูล texStorage2D() ที่เปลี่ยนแปลงไม่ได้จะไม่สามารถปรับขนาดได้เมื่อองค์ประกอบเปลี่ยนขนาด
// Binds the texture, and allocates its storage on the first upload, and when
// the element size changes. `state` stores the allocated size.
function allocateElementTexture(gl, texture, state, width, height) {
  gl.bindTexture(gl.TEXTURE_2D, texture);
  if (state.width === width && state.height === height) return;

  gl.texImage2D(
    gl.TEXTURE_2D,
    0,                // Mipmap level
    gl.RGBA8,         // Internal format: 8 bits per RGBA channel
    width,            // Size in canvas grid pixels
    height,
    0,                // Border: must be 0
    gl.RGBA,          // Format and type that match RGBA8
    gl.UNSIGNED_BYTE,
    null              // null reserves GPU memory without uploading pixels
  );
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  state.width = width;
  state.height = height;
}

const texture = gl.createTexture();
const textureState = { width: 0, height: 0 };
  • จากนั้นในบริบท WebGL ให้แสดงผลเนื้อหา HTML เป็นพื้นผิวโดยใช้เมธอด texElementSubImage2D โดยทำตามกฎต่อไปนี้
    • ส่ง { width, height } ในการกำหนดค่า ซึ่งจะทำให้สำเนาเติมเต็มพื้นผิวที่จัดสรรไว้ ไม่ว่า Canvas จะปรับขนาดอย่างไรก็ตาม
    • จัดสรรพื้นผิวใหม่หากขนาดมีการเปลี่ยนแปลง ดังที่แสดงด้านบน
// Uploads the element into the texture. Returns false if the element has no
// paint record yet. In that case, it's uploaded on the next paint event.
function uploadElementWebGL(gl, canvas, texture, element, state) {
  const size = getElementImageSize(canvas, element);
  if (!size) return false;
  const { width, height } = size;

  // Bind the texture, and reallocate it if the size changed (see above).
  allocateElementTexture(gl, texture, state, width, height);
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element, { width, height });
  return true;
}

canvas.onpaint = () => {
  try {
    uploadElementWebGL(gl, canvas, texture, uiElement, textureState);
  } catch (err) {
    console.error('texElementSubImage2D copy failed:', err);
  }
};
  • ในบริบท WebGPU ให้ใช้เมธอด drawElementImageToTexture โดยทำตามกฎต่อไปนี้
    • สร้างพื้นผิวด้วยการใช้งาน COPY_DST และ RENDER_ATTACHMENT และเพิ่ม TEXTURE_BINDING เพื่อสุ่มตัวอย่างในตัวปรับเฉดสี ระบบจะรายงานการใช้งานที่ไม่ถูกต้องเป็นข้อผิดพลาดในการตรวจสอบ WebGPU ไม่ใช่ข้อยกเว้น
    • ส่งขนาดพื้นผิวเป็น size หากไม่มี size องค์ประกอบที่ใหญ่กว่าจะถูกตัดให้พอดีกับพื้นผิว เมื่อใช้ size องค์ประกอบจะปรับขนาดให้พอดีกับพื้นที่
    • ปรับขนาดพื้นผิว WebGPU ไม่ได้ สร้างพื้นผิวใหม่เมื่อขนาดองค์ประกอบเปลี่ยนแปลง และสร้างกลุ่มการเชื่อมโยงที่อ้างอิงพื้นผิวดังกล่าวอีกครั้ง
// WebGPU textures can't be resized. Create the texture once, and recreate it
// when the element size changes.
function ensureElementTexture(device, state, width, height) {
  if (state.texture?.width === width && state.texture?.height === height) {
    return state.texture;
  }
  state.texture?.destroy();
  state.texture = device.createTexture({
    size: { width, height },
    format: "rgba8unorm",
    usage:
      GPUTextureUsage.TEXTURE_BINDING |
      GPUTextureUsage.COPY_DST |
      GPUTextureUsage.RENDER_ATTACHMENT,
  });
  return state.texture; // Recreate the bind groups that used the previous texture.
}

const textureState = { texture: null };

canvas.onpaint = () => {
  const size = getElementImageSize(canvas, uiElement);
  if (!size) return;
  const previousTexture = textureState.texture;
  const texture = ensureElementTexture(device, textureState, size.width, size.height);
  if (texture !== previousTexture) {
    // New texture: create, or rebuild, the bind groups that reference it.
  }
  try {
    device.queue.drawElementImageToTexture({ source: uiElement }, { texture, size });
  } catch (err) {
    console.error('drawElementImageToTexture copy failed:', err);
  }
};

เมื่อใช้ลูป requestAnimationFrame เพื่อแสดงผลฉาก ให้เรียกใช้ canvas.requestPaint() ภายในลูปเพื่อให้แน่ใจว่าเนื้อหา HTML แสดงผลใน Canvas event.changedElements แสดงรายการองค์ประกอบที่มีการเปลี่ยนแปลงการแสดงผล ตรวจสอบว่าคุณวาดองค์ประกอบใหม่หรืออัปโหลดพื้นผิวอีกครั้งเฉพาะในกรณีที่มีการอัปเดตองค์ประกอบ HTML ที่สืบทอดมาเท่านั้น

function render() {
  // Request to update the canvas
  canvas.requestPaint();
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

canvas.onpaint = (event) => {
  if (event.changedElements && event.changedElements.length > 0) {
    // Redraw the element, or update its texture, as shown above
  }
  // In WebGL and WebGPU, render the scene, and sync the element geometry as shown in step 6
};
  1. ซิงค์เรขาคณิตขององค์ประกอบ

เบราว์เซอร์จำเป็นต้องทราบตำแหน่งที่วาดองค์ประกอบ HTML เพื่อให้ตำแหน่ง DOM ตรงกับพิกเซลที่วาด ตัวอย่างเช่น การทดสอบตัวชี้สำหรับการคลิกและการเลือกข้อความ รวมถึงการช่วยเหลือพิเศษจะใช้เรขาคณิตนี้ โดยองค์ประกอบ drawable ที่ไม่มีการอัปเดตเรขาคณิตจะยังคงแสดงต่อต้นไม้การช่วยเหลือพิเศษ แต่ไม่มีข้อมูลเรขาคณิต Canvas จะทดสอบตัวชี้เฉพาะองค์ประกอบที่วาดด้วยเมธอด 2D drawElementImage() หรือลงทะเบียนด้วย updateElementGeometry() เท่านั้น

  • ในกรณีของบริบท 2 มิติ เบราว์เซอร์จะซิงค์องค์ประกอบโดยอัตโนมัติ drawElementImage() จะจัดแนวขอบเขตการทดสอบตัวชี้ DOM และวงแหวนโฟกัสของโปรแกรมอ่านหน้าจอให้ตรงกับพิกัดที่วาดไว้ คุณจึงไม่จำเป็นต้องตั้งค่า style.transform หากต้องการจัดการเรขาคณิตด้วยตนเอง เช่น เมื่อคุณวาดองค์ประกอบเดียวกันมากกว่า 1 ครั้ง ให้ส่ง { preserveElementGeometry: true } ไปยัง drawElementImage() และใช้การเปลี่ยนรูปแบบด้วย canvas.updateElementGeometry()

  • สำหรับกรณี 3 มิติที่มี WebGL หรือ WebGPU แคนวาสจะไม่สามารถบอกได้ว่าองค์ประกอบ HTML วาดอยู่ที่ใด คุณจึงต้องบอกแคนวาส คำนวณ DOMMatrix ที่แมปกล่องเส้นขอบขององค์ประกอบในหน่วยพิกเซล CSS กับ Canvas ในหน่วยพิกเซล CSS เบราว์เซอร์ต้องแมปจากพื้นที่พิกัด 3 มิติไปยังพื้นที่พิกัด CSS โดยใช้การเปลี่ยนรูปแบบวิวพอร์ต โดยทำดังนี้

    • ปรับองค์ประกอบ HTML ให้เป็นมาตรฐาน องค์ประกอบ HTML มีขนาดเป็นพิกเซล (เช่น กว้าง 200 พิกเซล) อย่างไรก็ตาม WebGL มักจะถือว่าออบเจ็กต์เป็น "สี่เหลี่ยมจัตุรัสหน่วย" เช่น ตั้งแต่ -0.5 ถึง 0.5 หากไม่ปรับให้เป็นมาตรฐาน ปุ่มขนาด 200 พิกเซลจะดูใหญ่ขึ้น 200 เท่า ขั้นตอนนี้ยังพลิกแกน Y ด้วย เนื่องจากใน CSS ค่าบวกจะอยู่ด้านล่าง แต่ใน WebGL ค่าบวกจะอยู่ด้านบน
    • แปลงเมทริกซ์ MVP ของ WebGL เป็นเมทริกซ์ DOM
    • แมปกับวิวพอร์ต Canvas ขั้นตอนนี้คือระยะ "การปรับขนาดใหม่" ซึ่งจะยืดคณิตศาสตร์พื้นที่หน่วยนั้นกลับออกไปเพื่อให้ตรงกับขนาดพิกเซล CSS ขององค์ประกอบ <canvas> บนหน้าจอ และพลิกแกน Y กลับ
    • คำนวณการเปลี่ยนรูปแบบสุดท้าย คูณเมทริกซ์ตามลำดับ: Viewport * MVP * Normalization การรวมการเปลี่ยนรูปแบบทั้ง 3 แบบเข้าด้วยกันเป็นการเปลี่ยนรูปแบบสุดท้ายจะสร้าง "แผนที่" ที่บอกเบราว์เซอร์อย่างชัดเจนว่าองค์ประกอบ HTML นั้นควรอยู่ตรงไหนเพื่อให้สอดคล้องกับภาพวาด 3 มิติ เบราว์เซอร์จะทำการแบ่งมุมมอง
    // Maps the element's border box (CSS pixels) to the canvas (CSS pixels), for a
    // quad that spans -0.5 to 0.5 in model space and is drawn with the `mvp` matrix.
    function computeCanvasTransform(canvas, element, mvp) {
      const width = element.offsetWidth;
      const height = element.offsetHeight;
    
      // 1. Normalize the HTML element (CSS pixels -> WebGL Model Space)
      const toGLModel = new DOMMatrix()
        // Scale pixels to 1 unit, flip Y (as in CSS it points down, and in WebGL it points up)
        .scale(1 / width, -1 / height, 1 / height)
        // Center the origin: (0,0) becomes (-width/2, -height/2) before scaling
        .translate(-width / 2, -height / 2);
    
      // 2. Convert WebGL MVP Matrix to DOM Matrix
      const mvpDOM = new DOMMatrix(Array.from(mvp));
    
      // 3. Map to the canvas viewport, in CSS pixels
      const clipToCanvasViewport = new DOMMatrix()
        // Move center (0,0) to center of canvas
        .translate(canvas.clientWidth / 2, canvas.clientHeight / 2)
        // Scale normalized clip (-1..1) to viewport size, and flip Y back
        .scale(canvas.clientWidth / 2, -canvas.clientHeight / 2, canvas.clientHeight / 2);
    
      // 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
      return clipToCanvasViewport.multiply(mvpDOM).multiply(toGLModel);
    }
    

จากนั้นส่งผ่านการเปลี่ยนรูปแบบไปยัง canvas.updateElementGeometry() เป็น canvasTransform ซึ่งจะย้ายองค์ประกอบ HTML ไปอยู่ด้านบนพิกเซลที่แสดงผลโดยตรง ซึ่งจะช่วยให้มั่นใจได้ว่าเมื่อผู้ใช้คลิกปุ่มหรือเลือกข้อความ ผู้ใช้จะกดองค์ประกอบ HTML จริงๆ เบราว์เซอร์ยังใช้การเปลี่ยนรูปแบบสำหรับขอบเขตการช่วยเหลือพิเศษขององค์ประกอบด้วย เช่น สำหรับวงแหวนโฟกัสของโปรแกรมอ่านหน้าจอ

  canvas.updateElementGeometry(targetHTMLElement, {
    canvasTransform: computeCanvasTransform(canvas, targetHTMLElement, htmlElementMVP),
  });

ใช้ตัวเลือกเหล่านี้เพื่อควบคุมการทดสอบตัวชี้

  • การเรียก updateElementGeometry() แต่ละครั้งจะย้ายองค์ประกอบไปยังด้านบนของลำดับการทดสอบตัวชี้ ส่งบอลให้ { preserveHitTestOrder: true } เพื่อรักษาตำแหน่งไว้
  • เรียกใช้ canvas.clearElementGeometry(element) เพื่อนำองค์ประกอบออกจากการทดสอบตัวชี้ เช่น เมื่อคุณซ่อนองค์ประกอบ

โค้ดตัวอย่าง

Canvas 2 มิติ

<canvas id="canvas" content="drawable" style="width: 400px; height: 200px;">
  <div id="ui-element" drawable>
    <p>
      This text is rendered inside the canvas but is present in the DOM tree.
    </p>
    <input type="email" name="email" placeholder="enter your email" />
    <button type="button">Submit</button>
  </div>
</canvas>

<script>
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  const uiElement = document.getElementById("ui-element");

  canvas.onpaint = () => {
    ctx.reset();
    // Draw the HTML element at x:0, y:0. The browser syncs the DOM location
    // with the drawn location.
    ctx.drawElementImage(uiElement, 0, 0);
  };

  // Handle resizing to match device pixels
  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);
    canvas.requestPaint();
  });

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

Canvas ของ WebGL

<canvas id="canvas" content="drawable" style="width: 400px; height: 400px;">
  <div id="ui-element" drawable>
    <p>WebGL UI Element</p>
    <button>Action</button>
  </div>
</canvas>

<script>
  const canvas = document.getElementById("canvas");
  const gl = canvas.getContext("webgl2");
  const uiElement = document.getElementById("ui-element");

  // getElementImageSize(), allocateElementTexture(), and uploadElementWebGL()
  // are defined in step 5.
  // computeCanvasTransform() is defined in step 6.

  // Setup WebGL texture...
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  const textureState = { width: 0, height: 0 };

  canvas.onpaint = () => {
    // 1. Update texture with HTML content
    try {
      uploadElementWebGL(gl, canvas, texture, uiElement, textureState);
    } catch (err) {
      console.error('texElementSubImage2D copy failed:', err);
    }

    // ... Render your 3D scene here, calculating htmlElementMVP matrix ...

    // 2. Sync DOM position, hit testing, and accessibility bounds with 3D scene
    canvas.updateElementGeometry(uiElement, {
      canvasTransform: computeCanvasTransform(canvas, uiElement, htmlElementMVP),
    });
  };
</script>

WebGPU Canvas

<canvas id="canvas" content="drawable" style="width: 400px; height: 400px;">
  <div id="ui-element" drawable>
    <p>WebGPU UI Element</p>
  </div>
</canvas>

<script>
  const canvas = document.getElementById("canvas");
  const context = canvas.getContext("webgpu");
  const uiElement = document.getElementById("ui-element");

  // getElementImageSize() and ensureElementTexture() are defined in step 5.
  // computeCanvasTransform() is defined in step 6.

  // Setup WebGPU...
  // const device = ...
  const textureState = { texture: null };

  canvas.onpaint = () => {
    // 1. Copy HTML content to a texture that matches the element size
    const size = getElementImageSize(canvas, uiElement);
    if (size) {
      const previousTexture = textureState.texture;
      const texture = ensureElementTexture(device, textureState, size.width, size.height);
      if (texture !== previousTexture) {
        // New texture: create, or rebuild, the bind groups that reference it.
      }
      try {
        device.queue.drawElementImageToTexture({ source: uiElement }, { texture, size });
      } catch (err) {
        console.error('drawElementImageToTexture copy failed:', err);
      }
    }

    // ... Render your 3D scene here, calculating htmlElementMVP matrix ...

    // 2. Sync DOM position, hit testing, and accessibility bounds (same matrix math as WebGL)
    canvas.updateElementGeometry(uiElement, {
      canvasTransform: computeCanvasTransform(canvas, uiElement, htmlElementMVP),
    });
  };
</script>

แนวทางปฏิบัติแนะนำ

  • จำเป็น: ตรวจสอบการรองรับเบราว์เซอร์สำหรับ HTML-in-Canvas API ก่อนใช้งาน
  • บังคับ: เพิ่มแอตทริบิวต์ content="drawable" ลงในองค์ประกอบ <canvas> เสมอ
  • จำเป็น: เพิ่มแอตทริบิวต์ drawable ลงในทุกองค์ประกอบที่คุณวาด รวมถึงองค์ประกอบย่อยโดยตรงของ Canvas
  • จำเป็น: ใช้แฮนเดิลเหตุการณ์ onpaint เพื่อแสดงผลเนื้อหา HTML ไปยัง Canvas
  • บังคับ: ใช้เมธอด drawElementImage ใน 2D หรือ drawElementImageToTexture ใน WebGPU เพื่อแสดงผลเนื้อหา HTML ไปยัง Canvas
  • บังคับ: ใน WebGL ให้จัดสรรบัฟเฟอร์พื้นผิวล่วงหน้าด้วย texImage2D() 1 ครั้ง (เมื่อเริ่มต้นหรือเมื่อขนาดองค์ประกอบเปลี่ยนแปลง) จัดสรรพื้นผิวใหม่เฉพาะเมื่อขนาดมีการเปลี่ยนแปลง และตั้งค่า TEXTURE_MIN_FILTER เป็น LINEAR
  • บังคับ: ใน WebGL ให้ใช้เมธอด texElementSubImage2D เพื่อแสดงผลเนื้อหา HTML เป็นพื้นผิวที่คุณจัดสรรไว้ล่วงหน้าด้วย texImage2D() ซึ่งมีขนาดตั้งแต่ captureElementImage()
  • จำเป็น: ใน WebGPU ให้ปรับขนาดพื้นผิวจาก captureElementImage() และสร้างใหม่เมื่อขนาดมีการเปลี่ยนแปลงเท่านั้น สร้างพื้นผิวด้วยการใช้งาน COPY_DST และ RENDER_ATTACHMENT และส่งขนาดพื้นผิวเป็น size
  • จำเป็น: ซิงค์เรขาคณิตขององค์ประกอบ HTML กับเนื้อหาที่แสดงผล ในแบบ 2 มิติ drawElementImage จะดำเนินการโดยอัตโนมัติ ใน WebGL และ WebGPU ให้เรียกใช้ canvas.updateElementGeometry(element, { canvasTransform })
  • บังคับ: ใช้ ResizeObserver เพื่อสังเกตขนาดหน้าจอและอัปเดตขนาด Canvas ให้ตรงกับพิกเซลของอุปกรณ์
  • อย่าฝังเนื้อหาแบบข้ามต้นทางใน Canvas เนื่องจากระบบไม่รองรับ
  • อย่าเริ่มต้น ResizeObserver ภายในเครื่องจัดการเหตุการณ์ onpaint เนื่องจากอาจทำให้หน่วยความจำรั่วไหล

กลยุทธ์สำรอง

ปัจจุบันเบราว์เซอร์ที่ทันสมัยบางรุ่นยังไม่รองรับ HTML-in-Canvas API ดังนั้นจึงมักต้องใช้กลยุทธ์สำรอง

อย่างไรก็ตาม เนื่องจาก API นี้มีประโยชน์ด้านประสิทธิภาพที่ดีขึ้น จึงควรใช้ HTML-in-Canvas หากเบราว์เซอร์รองรับ

กลยุทธ์สำรองจะขึ้นอยู่กับกรณีการใช้งาน ตัวอย่างเช่น สำหรับเนื้อหา HTML แบบอินเทอร์แอกทีฟใน Canvas หากระบบไม่รองรับ HTML-in-Canvas ให้วางเนื้อหา HTML ไว้ด้านบนของ Canvas โดยใช้ CSS

HTML-in-Canvas polyfills

ใช้สคริปต์ Polyfill ต่อไปนี้เพื่อเลียนแบบ HTML-in-Canvas API ในเบราว์เซอร์ที่ไม่รองรับ

  1. ติดตั้งหรือฝังไลบรารีโดยทำดังนี้

    # Install
    npm install three-html-render
    
    # Embed
    <script src="https://cdn.jsdelivr.net/npm/three-html-render/dist/polyfill.js"></script>
    
  2. เรียกใช้เมธอด installHtmlInCanvasPolyfill() เพื่อแปล HTML-in-Canvas