ขอแนะนำช่วงทดลองใช้ HTML-in-Canvas API จากต้นทาง

Thomas Nattestad
Thomas Nattestad

เป็นเวลาหลายปีที่นักพัฒนาเว็บต้องตัดสินใจเลือกสถาปัตยกรรมที่ยากลำบากเมื่อสร้างแอปพลิเคชันภาพที่ซับซ้อนและมีการโต้ตอบสูงบนเว็บ นั่นคือจะใช้ DOM สำหรับฟีเจอร์ความหมายที่หลากหลาย หรือจะแสดงผลโดยตรงไปยังองค์ประกอบ <canvas> เพื่อประสิทธิภาพกราฟิกในระดับต่ำ

HTML-in-Canvas API แบบทดลองใช้ใหม่ซึ่งพร้อมให้บริการแล้วในช่วงทดลองใช้จากต้นทางจะช่วยให้คุณไม่ต้องเลือก API นี้ช่วยให้คุณวาดเนื้อหา DOM ลงใน Canvas 2 มิติหรือพื้นผิว WebGL/WebGPU ได้โดยตรง ในขณะที่ยังคงให้ UI โต้ตอบได้ เข้าถึงได้ และเชื่อมต่อกับฟีเจอร์เบราว์เซอร์โปรด การผสานรวม HTML กับการประมวลผลกราฟิกในระดับต่ำจะช่วยให้คุณสร้างประสบการณ์ที่ก่อนหน้านี้ไม่สามารถทำได้

DOM กับ Canvas

หากต้องการทำความเข้าใจประสิทธิภาพของ API ใหม่นี้ คุณควรพิจารณาความแข็งแกร่งที่เกี่ยวข้องของทั้ง DOM และ Canvas

DOM เป็นองค์ประกอบหลักของ UI บนเว็บ โดยมีโซลูชันการจัดวางข้อความสำเร็จรูป ซึ่งใช้เนื้อหาที่เข้าใจความหมายเพื่อสร้างอินเทอร์เฟซที่สมบูรณ์ ซึ่งช่วยให้ผู้ใช้ดำเนินการทั่วไปในหน้าเว็บได้อย่างราบรื่น เช่น การไฮไลต์ข้อความเพื่อคัดลอก หรือการคลิกขวาที่รูปภาพเพื่อบันทึก นอกจากนี้ DOM ยังผสานรวมกับฟีเจอร์สำคัญของเบราว์เซอร์ ได้แก่ เครื่องมือช่วยเหลือพิเศษ แปลภาษา ค้นหาในหน้าเว็บ โหมดโปรแกรมอ่าน ส่วนขยาย โหมดมืด การซูมเบราว์เซอร์ และการป้อนข้อความอัตโนมัติ

ในทางกลับกัน Canvas (และ WebGL/WebGPU) จะอนุญาตให้เข้าถึงระดับต่ำเพื่อขับเคลื่อนตารางพิกเซลสำหรับกราฟิก 2 มิติและ 3 มิติขั้นสูง เกมและเว็บแอปที่ซับซ้อน (เช่น Google เอกสารหรือ Figma) ต้องใช้การเข้าถึงระดับต่ำที่มีประสิทธิภาพนี้ เนื่องจาก Canvas เป็นตารางพิกเซลโดยพื้นฐาน การรองรับฟีเจอร์ต่างๆ เช่น ข้อความที่ปรับเปลี่ยนตามพื้นที่ จึงต้องใช้ตรรกะ UI ที่กำหนดเองที่ซับซ้อน ซึ่งจะเพิ่มขนาด Bundle อย่างมาก ที่สำคัญ ฟีเจอร์เบราว์เซอร์ที่มีประสิทธิภาพทั้งหมดที่ผสานรวมเข้ากับ DOM จะหยุดทำงานโดยสิ้นเชิงเมื่อ UI ติดอยู่ในตารางพิกเซล Canvas แบบคงที่

ข้อดีของการนำ DOM ไปยัง Canvas

HTML-in-Canvas API เป็นสะพานที่ช่วยให้คุณได้ประโยชน์จากทั้ง 2 อย่าง การวาง HTML ไว้ภายในองค์ประกอบ <canvas> และการซิงค์การเปลี่ยนรูปแบบจะช่วยให้มั่นใจได้ว่าเนื้อหายังคงโต้ตอบได้อย่างเต็มที่ และการผสานรวมเบราว์เซอร์ทั้งหมดจะทำงานโดยอัตโนมัติ

สิ่งที่คุณจะได้รับจากการให้ DOM จัดการ UI ภายในองค์ประกอบ <canvas> มีดังนี้

  • การจัดวางและจัดรูปแบบข้อความ: การจัดวางและจัดรูปแบบข้อความที่เรียบง่าย รวมถึงข้อความหลายบรรทัดหรือข้อความสองทิศทางที่มีการใช้สไตล์ CSS
  • การควบคุมแบบฟอร์ม: การควบคุมแบบฟอร์มที่สื่อความหมายและใช้งานง่ายขึ้นพร้อมตัวเลือกการปรับแต่งที่หลากหลาย
  • การเลือกข้อความ การคัดลอก/วาง และการคลิกขวา: ผู้ใช้สามารถไฮไลต์ข้อความภายในฉาก 3 มิติ หรือคลิกขวาที่เมนูตามบริบทได้โดยตรง
  • การเลือกข้อความ การคัดลอก/วาง และการคลิกขวา: ผู้ใช้สามารถไฮไลต์ข้อความภายในฉาก 3 มิติ หรือคลิกขวาที่เมนูตามบริบทได้โดยตรง
  • การช่วยเหลือพิเศษ: เนื้อหาที่แสดงใน Canvas จะแสดงในโครงสร้างการช่วยเหลือพิเศษ ระบบการช่วยเหลือพิเศษสามารถแยกวิเคราะห์ UI ได้เช่นเดียวกับ HTML ปกติ และแสดงต่อระบบต่างๆ เช่น โปรแกรมอ่านหน้าจอ
  • Find-in-page: ผู้ใช้สามารถใช้ฟีเจอร์ค้นหาในหน้าเว็บ (Ctrl/Cmd+F) เพื่อค้นหาข้อความ และเบราว์เซอร์จะไฮไลต์ข้อความนั้นโดยตรงภายในพื้นผิว WebGL
  • Find-in-page: ผู้ใช้สามารถใช้ฟีเจอร์ค้นหาในหน้าเว็บ (Ctrl/Cmd+F) เพื่อค้นหาข้อความ และเบราว์เซอร์จะไฮไลต์ข้อความนั้นโดยตรงภายในพื้นผิว WebGL
  • ความสามารถในการจัดทำดัชนีและอินเทอร์เฟซ AI Agent: Web Crawler และ AI Agent สามารถจัดทำดัชนีและอ่านข้อความที่แสดงในฉาก 2 มิติและ 3 มิติได้อย่างราบรื่น
  • การผสานรวมส่วนขยาย: ส่วนขยายของเบราว์เซอร์จะทำงานโดยค่าเริ่มต้น เช่น ส่วนขยายการแทนที่ข้อความจะอัปเดตข้อความที่แสดงในโมเดล 3 มิติโดยอัตโนมัติ
  • การผสานรวม DevTools: คุณสามารถตรวจสอบเนื้อหา Canvas รวมถึงองค์ประกอบ UI ของ WebGL/WebGPU ได้โดยตรงในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ปรับแต่งสไตล์ CSS ในเครื่องมือตรวจสอบ แล้วดูการอัปเดตพื้นผิว 3 มิติได้ทันที

กรณีการใช้งานระดับสูง

API นี้ปลดล็อกศักยภาพอันน่าทึ่งในหลายโดเมน ดังนี้

  • แอปพลิเคชันขนาดใหญ่ที่ใช้ Canvas: ตอนนี้เว็บแอปที่มีขนาดใหญ่ เช่น Google เอกสาร, Miro หรือ Figma สามารถแสดงผลคอมโพเนนต์ UI ของแอปพลิเคชันที่ซับซ้อนในพื้นที่ทำงานที่ขับเคลื่อนด้วย Canvas ได้โดยตรง ซึ่งจะช่วยปรับปรุงการเข้าถึงและลดขนาดของแพ็กเกจ
  • ฉากและเกม 3 มิติ: ตอนนี้เว็บไซต์การตลาด ประสบการณ์ WebXR ที่สมจริง และเกมบนเว็บสามารถวาง UI ของเว็บที่โต้ตอบได้อย่างเต็มที่ลงในฉาก 3 มิติได้แล้ว เช่น หนังสือ 3 มิติที่ใช้ข้อความ DOM จริง หรือเทอร์มินัลในเกมที่รองรับการคัดลอกและวางโดยกำเนิด

วิธีใช้ API

การใช้ API จะเกิดขึ้นใน 3 เฟส ได้แก่ การตั้งค่า Canvas การแสดงผลใน Canvas และการอัปเดตการเปลี่ยนรูปแบบ CSS เพื่อให้เบราว์เซอร์ทราบตำแหน่งที่องค์ประกอบอยู่บนหน้าจอ

ข้อกำหนดเบื้องต้น

HTML-in-Canvas API อยู่ในช่วงทดลองใช้จากต้นทางใน Chrome 148 ถึง 150 หากต้องการทดสอบในเว็บไซต์ ให้ใช้ Chrome Canary 149 ขึ้นไปโดยเปิดใช้ Flag chrome://flags/#canvas-draw-element หากต้องการเปิดใช้ API สำหรับผู้ใช้รายอื่น ให้ลงทะเบียนช่วงทดลองใช้ Origin

ขั้นตอนที่ 1: การตั้งค่า Canvas ขั้นพื้นฐาน

ก่อนอื่น ให้เพิ่มแอตทริบิวต์ layoutsubtree ลงในแท็ก <canvas> ซึ่งจะทำให้เบราว์เซอร์ทราบถึงเนื้อหาที่ซ้อนอยู่ภายใน Canvas เตรียมพร้อมที่จะแสดงเนื้อหาภายใน Canvas และเปิดเผยเนื้อหาดังกล่าวต่อโครงสร้างการช่วยเหลือพิเศษ

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

ปรับขนาดตารางกริดของ Canvas

โปรดตรวจสอบว่าได้ปรับขนาดตารางกริดของ 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);

ขั้นตอนที่ 2: การแสดงผล

สำหรับบริบท 2 มิติ ให้ใช้เมธอด drawElementImage ทําเช่นนี้ภายใน paint event ซึ่งจะทริกเกอร์เมื่อใดก็ตามที่องค์ประกอบวาดใหม่ เช่น ระหว่างการไฮไลต์ข้อความหรือข้อมูลจากผู้ใช้ คุณต้องอัปเดตการเปลี่ยน CSS ขององค์ประกอบด้วยค่าที่ส่งคืนเพื่อให้การโต้ตอบทํางานต่อไป

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...
};

แสดงผลด้วย WebGL

สำหรับ WebGL คุณใช้ texElementImage2D ซึ่งทำงานคล้ายกับ texImage2D แต่ใช้ DOM Element เป็นแหล่งที่มา

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

แสดงผลด้วย WebGPU

WebGPU ใช้เมธอด copyElementImageToTexture ในคิวอุปกรณ์ ซึ่งคล้ายกับ copyExternalImageToTexture ดังนี้

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

ขั้นตอนที่ 3: อัปเดตการเปลี่ยนรูปแบบ CSS

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

สำหรับกรณีบริบท 2 มิติ ให้ใช้การแปลงที่การเรียกการแสดงผลส่งคืนไปยัง .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();
};

เมื่อใช้ WebGL หรือ WebGPU ตำแหน่งบนหน้าจอขององค์ประกอบจะขึ้นอยู่กับวิธีที่โค้ด Shader ใช้เท็กซ์เจอร์เอาต์พุต และไม่สามารถอนุมานจากบริบทการแสดงผล Canvas ได้ อย่างไรก็ตาม หากโปรแกรม Shader ใช้การฉายภาพมุมมองโมเดลทั่วไปเพื่อวาดพื้นผิว คุณจะใช้ฟังก์ชันอำนวยความสะดวกใหม่ element.getElementTransform() เพื่อคำนวณการเปลี่ยนรูปแบบที่ใช้ในลักษณะเดียวกับค่าที่ส่งคืนจาก drawElementImage() ได้ หากต้องการดำเนินการดังกล่าว คุณต้องทำดังนี้

  • แปลง MVP Matrix ของ WebGL เป็น DOM Matrix
  • ทำให้องค์ประกอบ HTML เป็นมาตรฐาน องค์ประกอบ HTML มีขนาดเป็นพิกเซล (เช่น กว้าง 200 พิกเซล) อย่างไรก็ตาม โดยปกติแล้ว WebGL จะถือว่าออบเจ็กต์เป็น "สี่เหลี่ยมจัตุรัสหน่วย" เช่น ตั้งแต่ 0 ถึง 1 หากไม่ปรับให้เป็นมาตรฐาน ปุ่มขนาด 200 พิกเซลจะดูใหญ่ขึ้น 200 เท่า
  • แมปกับวิวพอร์ต Canvas ขั้นตอนนี้คือเฟส "การปรับขนาด" ซึ่งจะขยายการคำนวณพื้นที่หน่วยนั้นกลับไปให้ตรงกับขนาดพิกเซลจริงขององค์ประกอบ <canvas> บนหน้าจอ นอกจากนี้ยังพลิกแกน Y ด้วย เนื่องจากใน WebGL ขึ้นคือบวก แต่ใน CSS ลงคือบวก
  • คำนวณการแปลงขั้นสุดท้าย คูณเมทริกซ์ตามลำดับ: Viewport * MVP * Normalization. การรวมเมทริกซ์เป็นทรานส์ฟอร์มสุดท้ายจะสร้าง "แผนที่" ที่บอกเบราว์เซอร์อย่างชัดเจนว่าเลเยอร์องค์ประกอบ HTML นั้นควรอยู่ที่ใดเพื่อให้สอดคล้องกับการวาดภาพ 3 มิติ
  • ใช้การเปลี่ยนรูปแบบกับองค์ประกอบ HTML ซึ่งจะย้ายเลเยอร์องค์ประกอบ HTML ไปอยู่ด้านบนสุดของพิกเซลที่แสดงผล ซึ่งจะช่วยให้มั่นใจได้ว่าเมื่อผู้ใช้คลิกปุ่มหรือเลือกข้อความ ผู้ใช้จะคลิกองค์ประกอบ HTML จริง
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();
  }
}

การรองรับไลบรารีและเฟรมเวิร์ก

ไลบรารียอดนิยมบางรายการได้เปิดตัวการรองรับฟีเจอร์ HTML ใน Canvas แล้ว

Three.js

การอัปเดตเมทริกซ์ด้วยตนเองอาจเป็นเรื่องน่าเบื่อ ซึ่งเป็นเหตุผลที่เฟรมเวิร์กต่างๆ เริ่มหันมาใช้การอัปเดตอัตโนมัติ Three.js มีการรองรับเวอร์ชันทดลองโดยใช้ THREE.HTMLTexture ใหม่

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 ยังรองรับ HTML ใน Canvas โดยใช้ Texture API ของตนเองด้วย

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

การสาธิต

ก่อนลองใช้เดโม โปรดตรวจสอบว่าสภาพแวดล้อมได้รับการกำหนดค่าอย่างถูกต้อง

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

  • หนังสือ 3 มิติ: หนังสือ 3 มิติที่แสดงผลด้วย WebGL ซึ่งใช้เลย์เอาต์ HTML สำหรับหน้าต่างๆ ผู้ใช้สามารถสลับแบบอักษรด้วย CSS ได้ เนื่องจากเป็นการแปลที่อิงตาม DOM การแปลในตัวจึงทำงานได้ทันที และเอเจนต์ AI สามารถดึงข้อความออกมาได้โดยมีความซับซ้อนน้อยลง
  • UI 3 มิติแบบอินเทอร์แอกทีฟ: แถบเลื่อนเยลลี่ WebGPU ที่หักเหแสงตามโมเดล 3 มิติพื้นฐาน ขณะที่ยังคงตอบสนองต่อแอตทริบิวต์ <input type="range"> step ของ HTML มาตรฐาน
  • พื้นผิวแบบเคลื่อนไหว: ป้ายบิลบอร์ด 3 มิติแบบไดนามิกที่แสดงผลดินสอ SVG แบบเคลื่อนไหวโดยใช้ DOM โดยตรงในพื้นผิว WebGL โดยไม่ต้องใช้ลูปภาพเคลื่อนไหวที่กำหนดเอง
  • ภาพซ้อนทับแบบหักเห: เลเยอร์การพิมพ์แบบอินเทอร์แอกทีฟที่บิดเบือนโดยเคอร์เซอร์ 3 มิติที่เคลื่อนไหว แต่ยังเลือกและค้นหาได้โดยใช้ฟีเจอร์ค้นหาในหน้าเว็บ

ดูคอลเล็กชันเดโมที่ชุมชนสร้างขึ้น หากต้องการให้เดโม HTML ใน Canvas แสดงในคอลเล็กชันนี้ โปรดสร้าง Pull Requestเพื่อเพิ่ม

ข้อจำกัด

แม้ว่า API จะมีประสิทธิภาพ แต่ก็มีข้อจำกัดบางประการที่ตั้งใจไว้ ดังนี้

  • เนื้อหาข้ามต้นทาง: API จะใช้กับเนื้อหา iframe ข้ามต้นทางไม่ได้เนื่องด้วยเหตุผลด้านความปลอดภัยและความเป็นส่วนตัว
  • การเลื่อนในเทรดหลัก: HTML ใน Canvas จะวาดด้วย JavaScript ซึ่งหมายความว่าการเลื่อนและภาพเคลื่อนไหวจะอัปเดตแยกจาก JavaScript ไม่ได้เหมือนกับที่ทำนอก Canvas นักพัฒนาแอปควรพิจารณาลักษณะประสิทธิภาพของการวางเนื้อหาที่เลื่อนได้ภายใน Canvas อย่างรอบคอบเทียบกับการให้ Canvas ทั้งหมดเลื่อน

ความคิดเห็น

หากคุณกำลังทดลองใช้ HTML-in-Canvas API เราอยากทราบความคิดเห็นจากคุณ คุณสามารถลงชื่อสมัครใช้ช่วงทดลองใช้จากต้นทางเพื่อเปิดใช้ฟีเจอร์ในเว็บไซต์ขณะที่ฟีเจอร์ยังอยู่ในระยะทดลองเพื่อช่วยเราออกแบบ API นอกจากนี้ คุณยังยื่นปัญหาเพื่อแสดงความคิดเห็นได้ด้วย

แหล่งข้อมูล