במשך שנים, מפתחי אתרים נאלצו לקבל החלטה ארכיטקטונית קשה כשהם בנו אפליקציות ויזואליות מורכבות ואינטראקטיביות מאוד באינטרנט: האם להסתמך על ה-DOM בגלל התכונות הסמנטיות העשירות שלו, או לעבד ישירות לאלמנט <canvas> כדי לקבל ביצועים גרפיים ברמה נמוכה?
עם HTML-in-Canvas API הניסיוני החדש, שזמין עכשיו בגרסת מקור לניסיון, אתם לא צריכים לבחור. באמצעות ה-API הזה אתם יכולים להוסיף תוכן DOM ישירות ל-canvas דו-ממדי או לטקסטורה של WebGL או WebGPU, ועדיין לשמור על ממשק משתמש אינטראקטיבי ונגיש שמחובר לתכונות הדפדפן המועדפות עליכם. השילוב של HTML עם עיבוד גרפי ברמה נמוכה מאפשר ליצור חוויות שבעבר לא היו אפשריות.
DOM לעומת Canvas
כדי להבין את העוצמה של ה-API החדש הזה, כדאי לבחון את היתרונות היחסיים של DOM ושל Canvas.
ה-DOM הוא המרכיב העיקרי של ממשק המשתמש באינטרנט. הוא מציע פתרונות מוכנים לפריסת טקסט, ומשתמש בתוכן שמובן מבחינה סמנטית כדי ליצור ממשקים עשירים. כך המשתמשים יכולים לבצע פעולות נפוצות בדפי אינטרנט בצורה חלקה – דברים שאנחנו בדרך כלל רואים כמובנים מאליהם, כמו הדגשת טקסט להעתקה או לחיצה ימנית על תמונה כדי לשמור אותה. ה-DOM משתלב גם עם תכונות חיוניות של הדפדפן: כלי נגישות, תרגום, חיפוש בדף, מצב קורא, תוספים, מצב כהה, זום בדפדפן ומילוי אוטומטי.
לעומת זאת, Canvas (וגם WebGL/WebGPU) מאפשר גישה ברמה נמוכה להפעלת רשת של פיקסלים לגרפיקה מתקדמת מאוד בדו-ממד ובתלת-ממד. משחקים ואפליקציות אינטרנט מורכבות (כמו Google Docs או Figma) דורשים גישה יעילה ברמה נמוכה. מכיוון שהקנבס הוא בעצם רשת של פיקסלים, כדי לתמוך בתכונות כמו טקסט רספונסיבי היה צריך להשתמש בלוגיקה מורכבת של ממשק משתמש בהתאמה אישית, מה שהגדיל באופן משמעותי את גודל החבילה. חשוב לציין שכל התכונות המתקדמות של הדפדפן שמשולבות ב-DOM מפסיקות לפעול לחלוטין כשממשק המשתמש לכוד בתוך רשת פיקסלים סטטית של רכיב canvas.
היתרונות של הוספת ה-DOM ל-Canvas
HTML-in-Canvas API הוא הגשר שמאפשר לכם ליהנות מהיתרונות של שני העולמות. כשמציבים HTML בתוך הרכיב <canvas> ומסנכרנים את הטרנספורמציה שלו, התוכן נשאר אינטראקטיבי לחלוטין וכל השילובים של הדפדפן פועלים באופן אוטומטי.
הנה מה שמקבלים כשמאפשרים ל-DOM לטפל בממשק המשתמש בתוך רכיב <canvas>:
- פריסה ועיצוב של טקסט: פריסה ועיצוב פשוטים של טקסט, כולל טקסט בכמה שורות או טקסט דו-כיווני עם סגנונות CSS.
- פקדי טפסים: פקדי טפסים אקספרסיביים וקלים יותר לשימוש עם אפשרויות התאמה אישית נרחבות.
- בחירת טקסט, העתקה/הדבקה ולחיצה ימנית: המשתמשים יכולים להדגיש טקסט בתוך הסצנות בתלת-ממד, או ללחוץ לחיצה ימנית על תפריטי הקשר באופן טבעי.
- נגישות: תוכן שמוצג בתוך ה-canvas נחשף לעץ הנגישות. מערכות נגישות יכולות לנתח את ממשק המשתמש כמו שהן מנתחות HTML רגיל, ולחשוף אותו למערכות כמו קוראי מסך.
- חיפוש בדף: המשתמשים יכולים להשתמש בחיפוש בדף (Ctrl/Cmd+F) כדי לחפש טקסט, והדפדפן מדגיש אותו ישירות בתוך הטקסטורות של WebGL.
- יכולת הוספה לאינדקס וממשק לסוכני AI: סורקי אינטרנט וסוכני AI יכולים להוסיף לאינדקס ולקרוא בצורה חלקה את הטקסט שמופיע בסצנות הדו-ממדיות והתלת-ממדיות שלכם.
- שילוב תוספים: תוספים לדפדפן פועלים באופן מקורי. לדוגמה, תוסף להחלפת טקסט מעדכן באופן אוטומטי את הטקסט שמוצג ברשתות התלת-ממדיות.
- שילוב של כלי הפיתוח: אתם יכולים לבדוק את תוכן ה-canvas, כולל רכיבים בממשק המשתמש של WebGL או WebGPU, ישירות בכלי הפיתוח ל-Chrome. אפשר לשנות סגנון CSS בכלי הבדיקה ולראות איך הוא מתעדכן באופן מיידי בטקסטורה התלת-ממדית.
תרחישי שימוש כלליים
ה-API הזה פותח פוטנציאל מדהים בכמה תחומים:
- אפליקציות גדולות שמבוססות על רכיבי Canvas: אפליקציות אינטרנט כבדות יכולות עכשיו לעבד רכיבים מורכבים של ממשק המשתמש של האפליקציה באופן מקורי בסביבות העבודה שלהן שמבוססות על רכיבי Canvas, וכך לשפר את הנגישות ולהפחית את משקל החבילה.
- סצנות ומשחקים בתלת-ממד: אתרי שיווק, חוויות WebXR סוחפות ומשחקים באינטרנט יכולים עכשיו להציב ממשק משתמש אינטראקטיבי לחלוטין בסצנות תלת-ממדיות – כמו ספר תלת-ממדי שמשתמש בטקסט DOM אמיתי, או מסוף במשחק שתומך באופן מקורי בהעתקה ובהדבקה.
איך משתמשים ב-API?
השימוש ב-API מתבצע בשלושה שלבים: הגדרת ה-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 לגורם לקביעת קנה המידה של המכשיר.
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: עיבוד (rendering)
בהקשר דו-ממדי, משתמשים ב-method 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. זה חשוב כדי שהדפדפן יוכל למפות בצורה נכונה את אזור האירוע – למשל, איפה בדיוק המשתמש לוחץ או מעביר את העכבר – עם המקום שבו הרכיב מוצג.
במקרה של הקשר דו-ממדי, צריך להחיל את הטרנספורמציה שהוחזרה על ידי קריאת העיבוד על ה-.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(). כדי לעשות את זה, צריך לבצע את הפעולות הבאות:
- המרת מטריצת MVP של WebGL למטריצת DOM.
- מנרמלים את רכיב ה-HTML. הגודל של רכיבי HTML נמדד בפיקסלים (לדוגמה, רוחב של 200 פיקסלים). לעומת זאת, ב-WebGL בדרך כלל מתייחסים לאובייקטים כ "ריבועי יחידה", למשל, בטווח שבין 0 ל-1. אם לא תבצעו נרמול, כפתור בגודל 200 פיקסלים ייראה גדול פי 200.
- ממפים לאזור התצוגה של אזור העריכה. השלב הזה הוא שלב "שינוי קנה המידה". הוא מרחיב את החישוב של מרחב היחידה בחזרה כדי להתאים למידות הפיקסלים בפועל של רכיב ה-
<canvas>במסך. הוא גם הופך את ציר ה-Y, כי ב-WebGL, למעלה הוא חיובי, אבל ב-CSS, למטה הוא חיובי. - מחשבים את הטרנספורמציה הסופית. מכפילים את המטריצות לפי הסדר:
Viewport * MVP * Normalization.השילוב שלהן לטרנספורמציה סופית אחת יוצר "מפה" שאומרת לדפדפן בדיוק איפה שכבת רכיב ה-HTML צריכה להיות כדי להתאים לשרטוט התלת-ממדי. - מחילים את הטרנספורמציה על רכיב ה-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();
}
}
תמיכה בספריות וב-frameworks
חלק מהספריות הפופולריות כבר כוללות תמיכה בתכונה 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 באמצעות ה-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. אנחנו כבר רואים פתרונות יצירתיים מהקהילה, החל מספרים תלת-ממדיים שניתן לתרגם ועד לרכיבים בממשק המשתמש שמשתקפים דרך הצללות זכוכית:
- הספר בתלת-ממד: ספר בתלת-ממד שעבר עיבוד ב-WebGL ומשתמש בפריסת HTML לדפים שלו. המשתמשים יכולים להחליף גופנים באמצעות CSS. מכיוון שהיא מבוססת על DOM, התכונה המובנית לתרגום פועלת באופן מיידי, וסוכני AI יכולים לחלץ את הטקסט בצורה פשוטה יותר.
- ממשקי משתמש אינטראקטיביים בתלת-ממד: סליידר ג'לי ב-WebGPU ששובר את האור על סמך מודל תלת-ממדי בסיסי, ועדיין מגיב למאפייני שלבים רגילים של HTML
<input type="range">. - טקסטורות מונפשות: שלט חוצות דינמי בתלת-ממד שמעבד עיפרון SVG מונפש באמצעות ה-DOM ישירות לטקסטורת WebGL, בלי צורך בלולאת אנימציה מותאמת אישית.
- שכבות-על רפרקטיביות: שכבת טיפוגרפיה אינטראקטיבית שמעוותת על ידי סמן תלת-ממדי נע, אבל ניתנת לבחירה ולחיפוש באמצעות חיפוש בדף.
כדאי לעיין באוסף ההדגמות שיצרה הקהילה. אם אתם רוצים שהדגמת ה-HTML-in-Canvas שלכם תוצג באוסף הזה, אתם יכולים ליצור בקשת משיכה כדי להוסיף אותה.
מגבלות
למרות שה-API הוא כלי חזק, יש לו כמה מגבלות מכוונות:
- תוכן ממקורות שונים: מסיבות שקשורות לאבטחה ולפרטיות, ה-API לא פועל עם תוכן iframe ממקורות שונים.
- גלילה בשרשור הראשי: HTML-in-canvas משורטט באמצעות JavaScript, מה שאומר שאי אפשר לעדכן את הגלילה והאנימציות בנפרד מ-JavaScript, כמו שאפשר לעשות מחוץ ל-canvas. המפתחים צריכים לשקול היטב את מאפייני הביצועים של הצבת תוכן שניתן לגלול בתוך אזור העריכה לעומת גלילה של כל אזור העריכה.
משוב
אם אתם מתנסים ב-HTML-in-Canvas API, נשמח לשמוע מכם. אתם יכולים להירשם לגרסת המקור לניסיון כדי להפעיל את התכונה באתר שלכם כשהיא בשלב הניסיוני, ולעזור לנו לעצב את ה-API. אפשר גם לדווח על בעיה כדי לשלוח משוב.
משאבים
- תמיכה ב-HTML-in-Canvas ב-Three.js
- הדגמה של HTML-in-Canvas ב-Three.js
- תמיכה ב-HTML-in-Canvas ב-PlayCanvas: מאמרי עזרה למפתחים
- HTML-in-Canvas in PlayCanvas demo
- HTML-in-Canvas: Explainer
- הנחיות Modern Web לכלי תכנות מבוססי-AI ל-HTML-in-Canvas
- הדגמות של HTML-in-Canvas ב-Chrome.dev
- אוסף הדגמות מדהים של HTML ב-Canvas שנוצר על ידי הקהילה