ข้อมูลอัปเดตเกี่ยวกับ HTML ใน Canvas: การทำซ้ำเพื่อมุ่งสู่ Web API ที่ดียิ่งขึ้น

Chrome 150 และ 155 ได้ทำการเปลี่ยนแปลงหลายอย่างใน HTML-in-Canvas API การเปลี่ยนแปลงเหล่านี้ ตอบสนองต่อความคิดเห็นจากชุมชนและมาตรฐานโดยมีเป้าหมายเพื่อปรับปรุง การยศาสตร์ของ API บล็อกโพสต์นี้จะอธิบายการเปลี่ยนแปลงและวิธีตรวจสอบว่าการติดตั้งใช้งานของคุณเป็นเวอร์ชันล่าสุด

การอัปเดตนี้รวมถึงการเปลี่ยนชื่อแอตทริบิวต์และเมธอด การกำหนดให้มีการจัดสรรหน่วยความจำของเท็กซ์เจอร์ล่วงหน้าอย่างชัดเจน และการอัปเดตโมเดลการซิงค์ DOM นอกจากนี้ การทดลองใช้ HTML ใน Canvas ยัง ขยายเวลาแล้วด้วย ต่อไปนี้คือคำอธิบายเกี่ยวกับสิ่งที่จะเปลี่ยนแปลง สาเหตุ และวิธีอัปเดตฐานของโค้ด

การเปลี่ยนแปลงใน Chrome 150

ใน Chrome 150 เราได้เปลี่ยนลายเซ็นอาร์กิวเมนต์ใน WebGL และ WebGPU เพื่อ ขจัดความซ้ำซ้อนและให้สอดคล้องกับข้อกำหนดด้านกราฟิกสมัยใหม่

WebGL

เรานำอาร์กิวเมนต์ที่ซ้ำกัน (level, srcFormat, destType) ออกจาก texElementImage2D (ดูรายละเอียดเพิ่มเติมได้ที่การสนทนาเกี่ยวกับ WebGL)

// Old implementation (Chrome < 150)
const level = 0;
const internalFormat = gl.RGBA;
const srcFormat = gl.RGBA;
const destType = gl.UNSIGNED_BYTE;

gl.texElementImage2D(
  gl.TEXTURE_2D,
  level,
  internalFormat,
  srcFormat,
  destType,
  element
);

// New implementation (from Chrome 150)
const internalFormat = gl.RGBA8;
gl.texElementImage2D(gl.TEXTURE_2D, internalFormat, element);

WebGPU

เราได้กำหนดมาตรฐานcopyElementImageToTextureพารามิเตอร์เป็นออบเจ็กต์ sourceDict และ destDict (ดู GPUWeb PR#6250) ดังนี้

// Old implementation (Chrome < 150)
GPUQueue.copyElementImageToTexture(
  element,
  width,
  height,
  { texture: texture }
);

// New implementation (from Chrome 150)
const sourceDict = { source: element };
const destDict = {
  destination: { texture: texture },
  width: width,
  height: height
};

GPUQueue.copyElementImageToTexture(sourceDict, destDict);

การเปลี่ยนแปลงใน Chrome 155

Chrome 155 มีการปรับแต่งเพิ่มเติมตามความคิดเห็นของนักพัฒนาแอป เรากำลัง อัปเดตชื่อเมธอด กำหนดให้มีการจัดสรรหน่วยความจำของเท็กซ์เจอร์ล่วงหน้าอย่างชัดเจน รองรับ DOM Subtree ที่ซับซ้อน และยกเลิกการเชื่อมต่อการซิงค์ DOM

WebGPU

เปลี่ยนชื่อเมธอด WebGPU จาก copyElementImageToTexture เป็น drawElementImageToTexture แล้ว เมื่อเปลี่ยนชื่อแล้ว ระบบจะขยายพิกัดพารามิเตอร์ (sourceX, sourceWidth) และจัดกลุ่มขอบเขตปลายทางเป็นออบเจ็กต์ size (GPUExtent3D) มาตรฐาน

// BEFORE (Chrome 150)
const sourceDict = {
  source: myElement,
  sx: 0,
  sy: 0,
  swidth: 100,
  sheight: 100
};
const destDict = {
  destination: { texture: myTexture },
  width: 300,
  height: 200
};

queue.copyElementImageToTexture(sourceDict, destDict);

// AFTER (Chrome 155+)
const sourceDict = {
  source: myElement,
  sourceX: 0,
  sourceY: 0,
  sourceWidth: 100,
  sourceHeight: 100
};
const destDict = {
  texture: myTexture,
  size: { width: 300, height: 200 }
};

queue.drawElementImageToTexture(sourceDict, destDict);

WebGL

การเปลี่ยนแปลงใน WebGL สำหรับ HTML ใน Canvas มี 3 ด้าน ดังนี้

  • ชื่อเมธอดเปลี่ยนจาก texElementImage2D เป็น texElementSubImage2D`
  • ลายเซ็นของเมธอดมีการเปลี่ยนแปลง โดยที่สำคัญที่สุดคือมีการเพิ่มอาร์กิวเมนต์ xoffset และ yoffset เพื่อให้ระบุออฟเซ็ตในเท็กซ์เจอร์ปลายทางที่จะวาดองค์ประกอบได้โดยไม่บังคับ
  • เมธอดนี้จะไม่จัดสรรพื้นที่เก็บข้อมูลบัฟเฟอร์สำหรับเท็กซ์เจอร์ปลายทางโดยอัตโนมัติอีกต่อไป คุณต้องจัดสรรพื้นที่เก็บข้อมูลอย่างชัดเจนก่อนใช้ API ซึ่งคล้ายกับลักษณะการทำงานของ texSubImage2D
// 1. Capture natural layout size of the element
const elementImage = canvas.captureElementImage(element);

// 2. Pre-allocate texture backing once (or when element resizes)
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA8,
  Math.ceil(elementImage.width),
  Math.ceil(elementImage.height),
  0,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  null // null reserves VRAM without uploading pixels
);

// 3. Upload DOM into pre-allocated VRAM
gl.texElementSubImage2D(
  gl.TEXTURE_2D,
  0,                  // level
  0,                  // xoffset
  0,                  // yoffset
  element             // pass DOM element
);

แอตทริบิวต์ Canvas และองค์ประกอบย่อยที่วาดได้

เรากำลังอัปเดตวิธีเตรียม Canvas เพื่อแสดงผล HTML

เปลี่ยนชื่อ layoutsubtree เป็น content="drawable"

เราได้เปลี่ยนชื่อแอตทริบิวต์บูลีน layoutsubtree ในองค์ประกอบ <canvas> เป็น content="drawable" เพื่อให้สอดคล้องกับรูปแบบการตั้งชื่อแอตทริบิวต์ HTML (ดูปัญหา #169 ของ WICG เพื่อดูรายละเอียดการอภิปราย)

แอตทริบิวต์ drawable สำหรับรุ่นต่อๆ ไป

ก่อนหน้านี้ ระบบจะแสดงเฉพาะองค์ประกอบย่อยโดยตรงของ Canvas เท่านั้น ตอนนี้คุณสามารถเพิ่มแอตทริบิวต์ drawable ลงในองค์ประกอบลูกหลานใดๆ ที่ต้องการวาดแยกกันได้อย่างชัดเจน เพื่อรองรับลำดับชั้น DOM ที่ซับซ้อนโดยไม่ต้องทำให้มาร์กอัปแบน

<!-- BEFORE -->
<canvas layoutsubtree>
  <div id="badge">
    <h2>Player Stats</h2>
    <button id="actionBtn">Equip</button>
  </div>
</canvas>

<!-- AFTER (Chrome 155+) -->
<canvas content="drawable">
  <!-- Subtree A (Body and heading) -->
  <div id="badge" drawable>
    <h2>Player Stats</h2>
    <!-- Subtree B (Excluded from Subtree A; drawn and animated independently) -->
    <button id="actionBtn" drawable>Equip</button>
  </div>
</canvas>

องค์ประกอบที่มี drawable จะจับภาพทั้งซับทรียกเว้นลูกหลานที่ซ้อนกันซึ่งระบุ drawable ด้วย ซึ่งช่วยให้คอนเทนเนอร์หลักและองค์ประกอบย่อย เช่น ปุ่ม สามารถเคลื่อนไหวและวาดในคำสั่งวาดแยกกันได้

การซิงค์การทดสอบเพลง

ก่อนหน้านี้ นักพัฒนาแอปต้องบันทึกค่าที่ส่งคืนจากวิธีการวาดและส่งไปยัง element.style.transform ตอนนี้ drawElementImage() เมธอดจะแสดงผลเป็น void และการซิงโครไนซ์จะขึ้นอยู่กับบริบท

บริบท 2 มิติ: การซิงค์อัตโนมัติ

ใน Canvas แบบ 2 มิติ เบราว์เซอร์จะซิงโครไนซ์ขอบเขตการทดสอบการคลิก DOM และวงแหวนโฟกัสของโปรแกรมอ่านหน้าจอโดยอัตโนมัติกับพิกัดที่วาด คุณนำการส่งผ่านข้อมูลการแปลงด้วยตนเองทั้งหมดออกได้โดยทำดังนี้

// BEFORE
const transform = ctx.drawElementImage(element, x, y);
element.style.transform = transform.toString();

// AFTER (Chrome 155+)
ctx.drawElementImage(element, x, y);
// No style update needed: position & hit-testing sync automatically

หากต้องการจัดการรูปทรงเรขาคณิตด้วยตนเองใน 2 มิติ ให้ส่ง { preserveElementGeometry: true } ไปยัง drawElementImage() และใช้การเปลี่ยนรูปแบบโดยใช้ canvas.updateElementGeometry()

บริบท 3 มิติ: ชัดเจน updateElementGeometry()

หากต้องการซิงโครไนซ์ DOM ในบริบท WebGL หรือ WebGPU ให้สร้างDOMMatrix transform แล้วส่งไปยัง canvas.updateElementGeometry()

canvas.addEventListener('paint', () => {
  // 1. Upload element snapshot to GPU texture
  device.queue.drawElementImageToTexture({ source: element }, { texture });

  // 2. Render 3D scene...
  drawScene();

  // 3. Construct transform matrix as needed
  const canvasTransform = new DOMMatrix().translate(x, y);

  // 4. Update DOM hit-testing and accessibility bounding boxes
  canvas.updateElementGeometry(element, { canvasTransform });
});

ความเข้ากันได้ข้ามเวอร์ชัน

หากแอปพลิเคชันของคุณต้องรองรับผู้ใช้ใน Chrome หลายเวอร์ชันในช่วงระยะเปลี่ยนผ่าน ให้ตรวจสอบว่ามีเมธอดที่อัปเดตแล้วในต้นแบบของเมธอดเหล่านั้นหรือไม่ และปรับการติดตั้งใช้งานตามนั้น

ตรวจสอบว่าคุณได้ตั้งค่าแอตทริบิวต์ขององค์ประกอบ <canvas> ที่ถูกต้องสำหรับเบราว์เซอร์ แต่ละเวอร์ชันแล้ว

// ============================================================================
//  Cross-version canvas markup
// ============================================================================
if (!canvas.hasAttribute('content') && !canvas.hasAttribute('layoutsubtree')) {
  if ('content' in HTMLCanvasElement.prototype) {
    // Chrome 155+ (New standardized attribute)
    canvas.setAttribute('content', 'drawable');
  } else {
    // Chrome < 155 (Legacy boolean attribute)
    canvas.setAttribute('layoutsubtree', '');
  }
}

สำหรับบริบท 2 มิติ ให้ตรวจสอบค่าที่ส่งคืนของ drawElementImage() และตรวจสอบ แอตทริบิวต์ต้นแบบ Canvas เพื่อรองรับทั้ง 2 เวอร์ชัน

// ============================================================================
// Cross-version 2D drawing & synchronization
// ============================================================================
function draw2DElement(ctx, element, x, y) {
  // Execute the 2D draw call
  const result = ctx.drawElementImage(element, x, y);

  if (result !== undefined && typeof result?.toString === 'function') {
    // Chrome < 155: drawElementImage returned a DOMMatrix
    // Requires manual transform piping to align DOM hit-test bounds:
    element.style.transform = result.toString();
  } else {
    // Chrome 155+: drawElementImage returns void / undefined
    // The browser automatically aligns DOM positioning, clicks, and a11y bounds!
    // If you previously had element.style.transform logic here, clear it:
    if (element.style.transform) {
      element.style.transform = "";
    }
  }
}

สำหรับ WebGPU ให้ใช้ลายเซ็นและชื่อเมธอดที่เข้ากันได้

// ============================================================================
// WebGPU cross-version support
// ============================================================================
if ('drawElementImageToTexture' in GPUQueue.prototype) {
  // Chrome 155+ (Latest implementation)
  queue.drawElementImageToTexture(
    { source: element, sourceX: 0, sourceY: 0, sourceWidth: w, sourceHeight: h },
    { texture: myTexture, size: { width: w, height: h } }
  );
} else if (typeof queue.copyElementImageToTexture === "function") {
  // Chrome 150 - 154: Try the Chrome 150+ dictionary signature first,
  // then fall back to the Chrome < 150 signature
  try {
    queue.copyElementImageToTexture(
      { source: element },
      { destination: { texture: myTexture }, width: w, height: h }
    );
  } catch (err) {
    // Chrome < 150
    queue.copyElementImageToTexture(element, { texture: myTexture });
  }
}

โปรดตรวจสอบความเข้ากันได้ของเบราว์เซอร์กับ WebGL

// ============================================================================
// WebGL cross-version support
// ============================================================================
if ('texElementSubImage2D' in WebGL2RenderingContext.prototype) {
  // Chrome 155+ (Explicit pre-allocation + sub-image)
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element);
} else if (gl.texElementImage2D && gl.texElementImage2D.length === 3) {
  // Chrome 150-154 (3-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, element);
} else if (gl.texElementImage2D) {
  // Chrome < 150 (6-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, element);
}

การขยายเวลา Origin Trial

ช่วงทดลองใช้ HTML ใน Canvas จากต้นทาง ได้รับการขยายเวลาไปจนถึง Chrome 160 อย่าลืมต่ออายุโทเค็นทดลองใช้ต้นทาง เพื่อให้บริการฟีเจอร์ HTML ใน Canvas แก่ผู้ใช้ต่อไป ดูรายละเอียดเพิ่มเติมได้ที่บทความเกี่ยวกับ Origin Trial

แชร์ความคิดเห็น

โปรดแจ้งให้เราทราบว่าการอัปเดตเหล่านี้ทำงานในแอปพลิเคชันของคุณอย่างไร โดย แจ้งปัญหาในที่เก็บ WICG บน GitHub สมัครรับจดหมายข่าวสำหรับนักพัฒนาแอป เพื่อติดตามข่าวสารล่าสุดเกี่ยวกับ HTML ใน Canvas