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

Thomas Nattestad
Thomas Nattestad

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

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

DOM กับ Canvas

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

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

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

ข้อดีของการนำ DOM ไปใช้ใน Canvas

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

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

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

ขั้นตอนที่ 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 ซึ่งจะทริกเกอร์เมื่อใดก็ตามที่องค์ประกอบวาดใหม่ เช่น ระหว่างการไฮไลต์ข้อความหรือข้อมูลจากผู้ใช้ คุณต้องอัปเดตการแปลง 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 เป็นแหล่งที่มา

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

  • แปลงเมทริกซ์ WebGL MVP เป็น เมทริกซ์ DOM
  • ทำให้องค์ประกอบ 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-in-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-in-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 ที่หักเหผ่าน Shader แก้ว ดังนี้

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

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

ข้อจำกัด

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

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

ความคิดเห็น

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

แหล่งข้อมูล