เป็นเวลาหลายปีแล้วที่นักพัฒนาเว็บต้องตัดสินใจเลือกสถาปัตยกรรมที่ยากลำบากเมื่อสร้างแอปพลิเคชันภาพที่ซับซ้อนและมีการโต้ตอบสูงบนเว็บ/ คุณควรใช้ 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 ที่กำหนดเองที่ซับซ้อน ซึ่งจะเพิ่มขนาดแพ็กเกจอย่างมาก ที่สำคัญคือฟีเจอร์เบราว์เซอร์ที่มีประสิทธิภาพทั้งหมดที่ผสานรวมอยู่ใน DOM จะใช้งานไม่ได้เลยเมื่อ UI ติดอยู่ในตารางกริดพิกเซล Canvas แบบคงที่
ข้อดีของการนำ DOM ไปใช้ใน Canvas
HTML-in-Canvas API เป็นสะพานที่ให้คุณได้ประโยชน์สูงสุดจากทั้ง 2 อย่าง การวาง HTML ไว้ในองค์ประกอบ <canvas> และซิงค์การแปลงจะช่วยให้มั่นใจได้ว่าเนื้อหาจะยังคงโต้ตอบได้อย่างเต็มที่ และการผสานรวมทั้งหมดของเบราว์เซอร์จะทำงานโดยอัตโนมัติ
สิ่งที่คุณจะได้รับจากการให้ DOM จัดการ UI ภายในองค์ประกอบ <canvas> มีดังนี้
- การจัดวางและจัดรูปแบบข้อความ: การจัดวางและจัดรูปแบบข้อความที่ง่ายขึ้น รวมถึงข้อความหลายบรรทัดหรือข้อความสองทิศทางที่มีการใช้สไตล์ CSS
- การควบคุมแบบฟอร์ม: การควบคุมแบบฟอร์มที่แสดงออกได้และใช้งานง่ายขึ้นพร้อมตัวเลือกการปรับแต่งที่หลากหลาย
- การเลือกข้อความ การคัดลอก/วาง และการคลิกขวา: ผู้ใช้สามารถไฮไลต์ข้อความภายในฉาก 3 มิติ หรือคลิกขวาเมนูบริบทได้โดยตรง
- การช่วยเหลือพิเศษ: เนื้อหาที่แสดงผลภายใน Canvas จะแสดงในแผนผังการช่วยเหลือพิเศษ ระบบการช่วยเหลือพิเศษสามารถแยกวิเคราะห์ UI ได้เช่นเดียวกับ HTML ปกติ และแสดง UI ให้ระบบต่างๆ เช่น โปรแกรมอ่านหน้าจอ
- ค้นหาในหน้าเว็บ: ผู้ใช้สามารถใช้ฟีเจอร์ค้นหาในหน้าเว็บ (Ctrl/Cmd+F) เพื่อค้นหาข้อความ และเบราว์เซอร์จะไฮไลต์ข้อความนั้นโดยตรงภายในพื้นผิว WebGL
- การจัดทำดัชนีและอินเทอร์เฟซที่ตัวแทน AI สามารถโต้ตอบได้: Web Crawler และตัวแทน AI สามารถจัดทำดัชนีและอ่านข้อความที่แสดงผลในฉาก 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 ตำแหน่งบนหน้าจอขององค์ประกอบจะขึ้นอยู่กับวิธีที่โค้ด Shader ใช้พื้นผิวเอาต์พุต และไม่สามารถอนุมานได้จากบริบทการแสดงผล Canvas อย่างไรก็ตาม หากโปรแกรม Shader ใช้การฉายภาพมุมมองโมเดลทั่วไปเพื่อวาดพื้นผิว คุณสามารถใช้ฟังก์ชันความสะดวกใหม่ 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 มีการรองรับในระยะทดลองใช้โดยใช้ experimental support โดยใช้ 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 นอกจากนี้ คุณยังยื่นรายงานปัญหาเพื่อแสดงความคิดเห็นได้ด้วย
แหล่งข้อมูล
- การรองรับ HTML-in-Canvas ใน Three.js
- การสาธิต HTML-in-Canvas ใน Three.js
- การรองรับ HTML-in-Canvas ใน PlayCanvas: เอกสารประกอบสำหรับนักพัฒนาซอฟต์แวร์
- การสาธิต HTML-in-Canvas ใน PlayCanvas
- HTML-in-Canvas: คำอธิบาย
- คำแนะนำในการพัฒนาเว็บที่ทันสมัยสำหรับเครื่องมือการเขียนโค้ด AI สำหรับ HTML-in-Canvas
- การสาธิต HTML-in-Canvas ใน Chrome.dev
- คอลเล็กชันการสาธิต HTML-in-Canvas ที่ยอดเยี่ยมจากชุมชน