‫HTML-in-Canvas: المفاهيم الفنية

تتيح لك واجهة برمجة التطبيقات HTML-in-Canvas عرض أشجار فرعية تفاعلية وسهلة الاستخدام من نموذج DOM مباشرةً في سياقات لوحة الرسم ثنائية الأبعاد أو WebGL أو WebGPU.

يوضّح هذا الدليل المفاهيم الفنية لـ HTML-in-Canvas الشائعة في جميع سياقات اللوحة:

إعداد اللوحة لعرض HTML

لعرض عناصر HTML داخل <canvas>، اضبط السمتَين التاليتَين:

  • ضبط content="drawable" على <canvas>: يتيح هذا الإجراء لعنصر canvas تنسيق عُقد DOM التابعة له بدلاً من التعامل معها فقط كمحتوى مخفي.
  • ضبط السمة drawable على العناصر المستهدَفة: يؤدي ذلك إلى وضع علامة على عنصر كجذر رسم مستقل مؤهَّل للعرض على لوحة الرسم.
<canvas id="mainCanvas" width="600" height="400" content="drawable">
  <div id="parentElement" class="card" drawable>
    <h2>Container Title</h2>
    <p>Descriptive text inside the parent element.</p>
    <!-- Nested drawable: excluded from parentElement and drawn independently -->
    <button id="childElement" class="btn" drawable>Action Button</button>
  </div>
</canvas>

فهم الشجرة الفرعية القابلة للرسم

عند إعداد HTML ليتم عرضه في لوحة رسم، عليك تحديد العناصر التي يجب رسمها بشكل مستقل. يمثّل كل عنصر drawable جذر رسم مستقل وشجرة فرعية قابلة للرسم.

تتكوّن الشجرة الفرعية القابلة للرسم من عنصر يحمل السمة drawable وجميع العناصر التابعة له، باستثناء أي عناصر تابعة متداخلة تحمل أيضًا السمة drawable، على سبيل المثال:

<canvas content="drawable">
  <!-- Subtree A (Renders parent container and text) -->
  <div id="parentElement" drawable>
    <h2>Parent Card Heading</h2>
    <p>Card body text content.</p>
    <!-- Subtree B (Excluded from Subtree A and drawn separately) -->
    <button id="nestedChildElement" drawable>
      Clickable Button
    </button>
  </div>
</canvas>
العنصر حدود الطلاء السلوك
#parentElement (الشجرة الفرعية "أ") لوحة الخلفية والعنوان والفقرة يتم عرضه عند استدعاء drawElementImage(parentElement, ...)
‫#nestedChildElement (الشجرة الفرعية ب) سطح الزر والنص تم استبعادها من الشجرة الفرعية #parentElement، ويتم عرضها في طلب drawElementImage(nestedChildElement, ...) منفصل
يتم دمج زر drawable داخل بطاقة drawable، ويتم رسم كل منهما من خلال استدعاء drawElementImage() الخاص به.

يتيح لك تداخل العناصر القابلة للرسم الحفاظ على علاقات HTML الدلالية مع منحك حرية وضع طبقات أو تحديد موضع أو تحريك العناصر الفرعية بشكل مستقل على مشهد لوحة الرسم.

فهم الحدث paint

توفّر واجهة HTML-in-Canvas API الحدث paint. يرسل المتصفّح تلقائيًا حدث paint على <canvas> كلما تغيّر التنسيق أو النص أو أنماط CSS في أي من العناصر الفرعية في drawable.

canvas.addEventListener("paint", (event) => {
  // Clear the viewport
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Redraw elements individually
  ctx.drawElementImage(parentElement, 20, 20);
  ctx.drawElementImage(childElement, 40, 90);
});

يرسل الحدث paint مثيلاً من CanvasPaintEvent، والذي يوسّع نطاق نموذج العناصر في المستند (DOM) العادي Event ويحتوي على السمة changedElements التي تتضمّن فقط العناصر التابعة drawable التي تغيّرت بشكل مرئي (على سبيل المثال، تغييرات في الأنماط أو تغييرات في التنسيق أو تعديلات في النص أو تغييرات في حالة التركيز) منذ آخر عملية عرض.

canvas.addEventListener("paint", (event) => {
  // Array of changed elements
  for (const el of event.changedElements) {
    console.log(`Element #${el.id} visually changed and needs repainting`);
  }
});
يُجري المتصفّح عملية رسم على لوحة الرسم كلما تغيّر عرض عنصر فرعي قابل للرسم.

مراحل نشاط الإطار

تظهر أوامر رسم Canvas التي يتم تنفيذها داخل الحدث paint في تعديل العرض الحالي، ولكن لا تظهر تعديلات DOM التي يتم إجراؤها داخل الحدث paint إلا في الإطار التالي:

canvas.addEventListener("paint", () => {
  // Renders in the CURRENT rendering update:
  ctx.drawElementImage(parentElement, 20, 20);

  // Modifying DOM styles inside paint schedules a new layout cycle;
  // changes will appear in the NEXT frame:
  parentElement.style.backgroundColor = "#e0f2fe";
});

لوحات العرض المدمجة

إذا كانت صفحتك تحتوي على عناصر <canvas content="drawable"> متداخلة، يرسل المتصفّح أحداث paint بترتيب عكسي لشجرة نموذج العناصر في المستند (يتم تشغيل العناصر التابعة قبل العناصر الرئيسية). يضمن ذلك عرض عناصر لوحة العرض الفرعية قبل أن تدمجها عناصر لوحة العرض الرئيسية.

لنفترض في المثال التالي أنّ العنصر nestedElement الداخلي يجب أن يتغيّر بشكل مرئي (على سبيل المثال، يتغيّر لون خلفية CSS عندما يمرّر المستخدم المؤشر فوقه). أولاً، يتم تنشيط الحدث paint على لوحة الرسم nestedCanvas. يمحو المستمع المخزن المؤقت الثانوي ويستدعي ctx.drawElementImage(nestedElement, ...) لعرض الحالة المعدَّلة. بعد ذلك، يتم تنشيط الحدث paint في parentCanvas. ويقوم المستمع برسم wrapperElement (الذي يحتوي على لوحة الرسم nestedCanvas التي تم تحديثها بالفعل) في المشهد.

<!-- Outer parent canvas -->
<canvas id="parentCanvas" width="800" height="600" content="drawable">
  <!-- Wrapper container for nested content -->
  <div id="wrapperElement" drawable>

    <!-- Nested child canvas -->
    <canvas id="nestedCanvas" width="300" height="200" content="drawable">
      <div id="nestedElement" class="badge" drawable>
        Inner Child Content
      </div>
    </canvas>

  </div>
</canvas>

<script>
const parentCanvas = document.getElementById("parentCanvas");
const parentCtx = parentCanvas.getContext("2d");

const nestedCanvas = document.getElementById("nestedCanvas");
const nestedCtx = nestedCanvas.getContext("2d");

const nestedElement = document.getElementById("nestedElement");
const wrapperElement = document.getElementById("wrapperElement");

// ============================================================================
// 1. Child Canvas Listener (Fires FIRST)
// ============================================================================
nestedCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the child canvas with the updated inner element
  nestedCtx.clearRect(0, 0, nestedCanvas.width, nestedCanvas.height);
  nestedCtx.drawElementImage(nestedElement, 10, 10);

  console.log("1. Child nested canvas rendered fresh pixels.");
});

// ============================================================================
// 2. Parent Canvas Listener (Fires SECOND)
// ============================================================================
parentCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the parent canvas scene
  parentCtx.clearRect(0, 0, parentCanvas.width, parentCanvas.height);

  // Draws the wrapper container (including the freshly painted nestedCanvas)
  parentCtx.drawElementImage(wrapperElement, 50, 50);

  console.log("2. Parent canvas composited the complete scene.");
});
</script>
عندما تكون عناصر <canvas content="drawable"> مدمجة، يتم تشغيل أحداث الرسم بترتيب شجرة عكسي: يتم رسم اللوحة المدمجة أولاً، حتى تتمكّن اللوحة الرئيسية من رسم وحدات البكسل المحدّثة.

تفعيل حدث "paint"

يمكنك استدعاء requestPaint() لجدولة حدث paint واحد في الإطار التالي، حتى إذا لم تتغيّر أي نصوص أو أنماط في نموذج DOM. ويمكن استخدام ذلك لعرض مشاهد تفاعلية تتضمّن عناصر متحركة، على سبيل المثال:

let animatedX = 0;

canvas.addEventListener("paint", () => {
  animatedX += 1.5; // Move the element horizontally
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawElementImage(staticElement, 20, 20);
  ctx.drawElementImage(movingElement, animatedX, 200);
  // Request canvas paint on each rendering update for smooth animation
  canvas.requestPaint();
});

canvas.requestPaint();

تعديل شكل عنصر

عند رسم عنصر HTML في لوحة رسم، يحدّد رمز الرسم مكان ظهور العنصر وحجمه. وعلى عكس محتوى HTML العادي، لا يمكن تحديد موضع العنصر على الشاشة وحجمه، أي هندسته، من خلال نمطه المحسوب وحده. يحتاج المتصفّح إلى هندسة العنصر لتنفيذ ما يلي:

  • إرسال أحداث الإدخال، مثل النقرات والنقرات على الشاشة وحركة المؤشر إلى العنصر الصحيح ويُعرف هذا الإجراء باسم اختبار مؤشر الماوس.
  • عرض نتائج دقيقة لطلبات البحث عن التنسيقات، مثل getBoundingClientRect()
  • الإبلاغ عن حدود العنصر إلى التكنولوجيا المساعِدة، مثل مكبّرات الشاشة

في الوضع الثنائي الأبعاد، يعدّل drawElementImage() الشكل الهندسي تلقائيًا. وتكفي الوسيطات التي يتم تمريرها إليه، بالإضافة إلى النمط المحسوب للعنصر والتحويل الحالي للسياق، لتحديد مكان ظهور العنصر.

في سياقات WebGL وWebGPU، لا يمكن للمتصفّح تحديد الشكل الهندسي. يتم نسخ عنصر HTML إلى مادة، ويمكن لبرنامج التظليل بعد ذلك ربط هذه المادة بالمشهد بأي طريقة عشوائية. لإعلام المتصفّح بمكان ظهور العنصر، يمكنك تمرير عملية تحويل إلى updateElementGeometry().

عندما يتوقف الرمز عن استدعاء updateElementGeometry()، لن يتمكّن المتصفّح من معرفة مكان عناصر HTML على البطاقة الثلاثية الأبعاد. تستمر البطاقة في الدوران، ولكن لا تزال النقرات تؤدي إلى المكان الذي كانت فيه.

لتعديل الشكل الهندسي، مرِّر تحويلاً مكانيًا إلى الطريقة. تعرَّف على كيفية إنشاء عملية تحويل لسياقات مختلفة:

إدارة ترتيب تجميع اختبارات مؤشر الماوس

بشكل تلقائي، في كل مرة يتم فيها تعديل شكل عنصر، ينقله المتصفّح إلى أعلى حزمة التفاعل (آخر عنصر تم رسمه = هدف النقر في الأعلى). يمكنك التحكّم في هذا السلوك باستخدام preserveHitTestOrder:

// Updates the backdrop without stealing clicks from foreground elements
canvas.updateElementGeometry(backgroundElement, {
  canvasTransform: customTransform,
  preserveHitTestOrder: true
});
Option السلوك حالة الاستخدام
preserveHitTestOrder: false (تلقائي) ينقل العنصر إلى أعلى حزمة اختبار مؤشر الماوس. عناصر المقدّمة التي يجب أن تتلقّى النقرات أولاً
preserveHitTestOrder: true يترك العنصر في أولوية التكديس الحالية. عناصر الخلفية التي يجب تعديلها بعد عناصر المقدّمة

إعادة ضبط الشكل الهندسي

لإلغاء رسم عنصر بدون إزالته من نموذج العناصر في المستند (DOM)، يمكنك استدعاء canvas.clearElementGeometry(). يؤدي ذلك إلى إعادة ضبط تحويل لوحة الرسم للعنصر ووضع علامة عليه كعنصر خارج الشاشة في شجرة تسهيل الاستخدام:

// Remove from canvas hit-testing and mark as offscreen
canvas.clearElementGeometry(childElement);

الحجم والوحدات

عند رسم HTML في لوحة رسم، يعمل المتصفّح في ثلاثة أنظمة إحداثيات في آنٍ واحد: وحدات بكسل CSS ووحدات بكسل الجهاز ووحدات بكسل شبكة لوحة الرسم.

تُستخدم وحدات بكسل CSS لقياس المحتوى وتحديد موضعه أثناء التنسيق. بعد ذلك، يغيّر المتصفّح حجمها بمقدار devicePixelRatio لتنقيط الصفحة إلى وحدات بكسل على الجهاز الفعلي. بما أنّ وحدات بكسل CSS هي وحدات منطقية، غالبًا ما تكون المواضع والأحجام كسورًا من وحدة بكسل الجهاز (على سبيل المثال، 10.5 بكسل CSS × نسبة بكسل الجهاز 1.5 = 15.75 بكسل الجهاز). يمكن للشاشة تلوين وحدات بكسل كاملة فقط، لذا على المتصفّح حلّ أي مشكلة تقع بينها. ويتم ذلك بطريقتَين:

  • المحاذاة: يتم تقريب حدود المربّعات والحدود وإزاحات الطبقات إلى أقرب بكسل كامل للجهاز لكي تظل واضحة.
  • صقل الحواف: بالنسبة إلى الرموز النصية والمنحنيات والحواف التي تم تحويلها، يتم دمج التغطية الجزئية في وحدات البكسل المجاورة لكي تظل تبدو سلسة وفي المكان المناسب.

يحتوي العنصر <canvas> على شبكة بكسل خاصة به، يتم ضبطها من خلال السمتَين width وheight، وهي منفصلة عن كل من وحدات بكسل CSS ووحدات بكسل الجهاز. لعرضها، يعيد المتصفّح أخذ عيّنات من الصورة النقطية للوحة الرسم على شبكة البكسل المادية، ويمزج وحدات بكسل لوحة الرسم المجاورة لملء كل بكسل في الجهاز. To learn how the browser maps between CSS, canvas, and device pixels, see Pixel-perfect rendering with devicePixelContentBox.

يتم وضع أي عناصر HTML من النوع drawable بوحدات بكسل CSS. بما أنّ المحتوى المرسوم في لوحة رسم يمكن أن يمرّ بعمليتَي تحويل، من وحدات بكسل CSS إلى شبكة لوحة الرسم ثم من شبكة لوحة الرسم إلى وحدات البكسل المادية، فإنّ كل خطوة لا تتوافق تمامًا تضيف جولة أخرى من المزج، وقد تبدو النتيجة غير واضحة. لتجنب ذلك، اجعل شبكة اللوحة تطابق شبكة البكسل المادية. يخدم هذا غرضين:

  • العرض المتّسق: إذا كانت اللوحة ترسم نسبة 1:1 على وحدات البكسل المادية، فإنّ تحويل وحدات بكسل CSS إلى وحدات بكسل اللوحة يؤدي إلى النتيجة نفسها التي يحقّقها تحويل المتصفّح من وحدات بكسل CSS إلى وحدات بكسل مادية لملفات HTML العادية.
  • عدم الحاجة إلى مزج إضافي: إذا كانت اللوحة تتطابق بنسبة 1:1 مع وحدات البكسل المادية، فإنّ تحويلها إلى وحدات بكسل الجهاز لا يضيف أي تمويه.

توضّح الصورة التالية تحويل وحدات البكسل الذي يؤدي إلى ظهور محتوى مموّه. لا تتم محاذاة الشبكات الثلاث: عنصر اللوحة هو 360 × 180 بكسل CSS، وحجم اللوحة هو 300 × 150 بكسل لشبكة اللوحة. عندما تكون نسبة وحدات البكسل على الجهاز 1.5، تغطي اللوحة 540 × 270 بكسل على الجهاز. يمتد بكسل CSS واحد على 1.5 بكسل للجهاز، ويمتد بكسل واحد على اللوحة على 1.2 بكسل CSS أو 1.8 بكسل للجهاز (اللوحة 1). عند رسم الدائرة، يكون موضعها ونصف قطرها بوحدات بكسل اللوحة، وليس بوحدات بكسل CSS، لذا يرسمها المتصفّح في صورة نقطية للوحة مقاس 300 × 150 وينعّم الحافة (اللوحة 2). لعرض لوحة الرسم، يمدّد المتصفّح هذه الصورة النقطية على 540 × 270 بكسل على الجهاز، لذا على المتصفّح إجراء عملية استكمال: يأخذ كل بكسل على الجهاز مزيجًا مرجّحًا من أقرب وحدات بكسل في لوحة الرسم، والتي تحتوي على الحافة التي تم تنعيمها. يتم دمج الحافة مرة ثانية وتنتشر عبر عدة وحدات بكسل للجهاز، لذلك تبدو الدائرة غير واضحة (اللوحة 3). يمكن تجنب ذلك إذا تطابقت شبكة اللوحة مع وحدات بكسل الجهاز بنسبة 1:1. بعد ذلك، سيعمل المتصفّح على تنعيم الحافة مرة واحدة فقط، مباشرةً على شبكة الجهاز، ويعرض الصورة النقطية بدون إعادة أخذ العينات، لتبقى الحافة واضحة (اللوحة 4):

مخطّط بياني من أربعة أقسام يقارن بين شبكات البكسل لحافة منحنية: 1. شبكات CSS (360 × 180) ولوحة الرسم (300 × 150) والجهاز (540 × 270) غير متوافقة. 2. تمت إزالة التعرّج من المنحنى على شبكة لوحة الرسم الخشنة التي تبلغ أبعادها 300 × 150. 3- تم استيفاء هذه الصورة النقطية على شبكة الجهاز التي تبلغ 540 × 270، مع تنعيم الحواف مرة ثانية وتمويه الحافة. 4. لوحة رسم بمقاس 540 × 270 تتطابق مع شبكة الجهاز بنسبة 1 إلى 1، مع تنعيم الحواف مرة واحدة للحصول على حافة واضحة.
كيف تتسبّب شبكات CSS ولوحة الرسم ووحدات بكسل الجهاز غير المتوافقة في حدوث تنعيم مزدوج (اللوحات من 1 إلى 3)، مقارنةً بشبكة لوحة الرسم إلى الجهاز بنسبة 1:1 (اللوحة 4).

للحصول على عرض واضح، يجب أن تتطابق شبكة اللوحة مع عدد وحدات بكسل الجهاز التي تغطيها اللوحة: 540 × 270 في المثال السابق.

مقارنة جنبًا إلى جنب لبطاقة HTML مكتوب عليها &quot;مرحبًا من نموذج DOM! هذه البطاقة هي HTML مرسومة في لوحة رسم.' على اليمين: رسم على شبكة لوحة الرسم التلقائية التي تبلغ 300 × 150، ويبدو ضبابيًا. على اليسار: رسم على شبكة لوحة رسم بحجم 800 × 400 مع ResizeObserver، تبدو واضحة.
اللوحة نفسها، 400 × 200 بكسل CSS، على شاشة بنسبة كثافة بكسل تبلغ 1.5. على اليمين: شبكة لوحة الرسم بدقة 400 × 200. على اليسار: تم تعديلها وفقًا لنسبة بكسل الجهاز، وهي شبكة ‎600 x 300.

تحديد حجم شبكة اللوحة باستخدام ResizeObserver

يعتمد حجم الشبكة المناسب على الجهاز، وقد يتغيّر عند تغيير التنسيق أو عندما يكبّر المستخدم الصفحة أو عندما ينقل النافذة إلى شاشة مختلفة. لذا، بدلاً من ترميز حجم الشبكة بشكل ثابت، دع المتصفّح يخبرك بما يجب أن يكون عليه.

للحفاظ على مزامنة شبكة لوحة العرض مع لوحة العرض على الشاشة، يجب دائمًا ضبط حجمها باستخدام ResizeObserver وضبط شبكة لوحة العرض على devicePixelContentBoxSize:

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

تحويل وحدات بكسل CSS إلى إحداثيات شبكة لوحة الرسم

غالبًا ما يجمع معالج paint بين قياسات DOM وطلبات الرسم، على سبيل المثال، لتحديد موضع عنصر استنادًا إلى حجمه. يستخدم كل منهما وحدات مختلفة:

  • تصف وحدات بكسل اللوحة شبكة اللوحة، والتي يتم تحديد حجمها من خلال canvas.width وcanvas.height. على سبيل المثال، المَعلمات dx وdy وdwidth وdheight لطرق الرسم drawElementImage() أو مَعلمات واجهات برمجة التطبيقات الأخرى للوحة الرسم، مثل fillRect() وtranslate().
  • تصف وحدات بكسل CSS الصفحة. يتم قياس DOM، مثل offsetWidth وclientWidth، وإحداثيات أحداث المؤشر، مثل offsetX، بوحدات بكسل CSS.

في ما يلي نظرة عامة على الوحدات التي تستخدمها مَعلمات واجهة برمجة التطبيقات المختلفة:

القيمة الوحدات
canvas.width وcanvas.height بكسلات شبكة اللوحة
مَعلمات الرسم الثنائي الأبعاد، بما في ذلك dx وdy وdwidth وdheight من drawElementImage() بكسلات شبكة اللوحة
تم إرجاع width وheight من قِبل "canvas.getElementImageDefaultSize()" بكسلات شبكة اللوحة
المستطيلات المصدر: sx وsy وswidth وsheight من drawElementImage() وtexElementSubImage2D()؛ sourceX وsourceY وsourceWidth وsourceHeight من drawElementImageToTexture() وحدات بكسل CSS
أحجام الزخرفة: width وheight خيار من إجمالي texElementSubImage2D()؛ size من إجمالي drawElementImageToTexture() بكسل الزخرفة حجم العنصر بالبكسل في شبكة لوحة العرض، ويتم تقريبه إلى الأعلى تلقائيًا
هندسة العناصر: getElementTransform()، وخيارا canvasTransform وcanvasClip لـ updateElementGeometry() وحدات بكسل CSS
قياسات DOM، مثل offsetWidth وclientWidth وgetBoundingClientRect() وإحداثيات أحداث المؤشر وcontentBoxSize لإدخال ResizeObserver وحدات بكسل CSS
‫devicePixelContentBoxSize من إدخال ResizeObserver وحدات البكسل على الجهاز

إذا استخدمت وحدات خاطئة، قد لا يتطابق حجم محتوى HTML الذي يتم رسمه في اللوحة مع توقعاتك. يجب تحويل قيمة بكسل CSS إلى بكسل شبكة لوحة الرسم عند رسم HTML في لوحة الرسم. لإجراء ذلك، عليك احتساب النسبة بين حجم شبكة اللوحة وحجم CSS للوحة: canvas.width / cssContentWidth أفقيًا وcanvas.height / cssContentHeight عموديًا. يُعدّ ResizeObserver مكانًا مناسبًا لاحتساب النسبة على النحو التالي:

// The number of canvas grid pixels per CSS pixel
let scaleX = 1;
let scaleY = 1;

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  const cssContentWidth = entry.contentRect.width;
  const cssContentHeight = entry.contentRect.height;

  // Best practice: make the canvas grid match the physical pixel grid
  if (dpc) {
    canvas.width = dpc[0].inlineSize;
    canvas.height = dpc[0].blockSize;
  } else {
    canvas.width = Math.round(cssContentWidth * window.devicePixelRatio);
    canvas.height = Math.round(cssContentHeight * window.devicePixelRatio);
  }

  scaleX = canvas.width / cssContentWidth;
  scaleY = canvas.height / cssContentHeight;
});

const supportsDevicePixelContentBox =
  typeof ResizeObserverEntry !== 'undefined' &&
  'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);

بعد ذلك، اضرب قيم وحدات بكسل CSS في scaleX وscaleY قبل تمريرها إلى طريقة الرسم. على سبيل المثال، لتوسيط عنصر قابل للرسم في لوحة العرض:

const ctx = canvas.getContext('2d');
const card = document.getElementById('card');

canvas.onpaint = () => {
  ctx.reset();

  // offsetWidth and offsetHeight are in CSS pixels.
  // Convert them to canvas grid coordinates.
  const width = card.offsetWidth * scaleX;
  const height = card.offsetHeight * scaleY;
  const x = (canvas.width - width) / 2;
  const y = (canvas.height - height) / 2;

  // drawElementImage() takes canvas grid coordinates.
  ctx.drawElementImage(card, x, y);
};

أخيرًا، بما أنّ drawElementImage() يرسم مربّع حدود العنصر، استخدِم box-sizing: border-box لتضمين المساحة المتروكة والحدود في width وheight للبطاقة، بحيث يتم رسم البطاقة بالحجم الذي تحدّده:

#card {
  /* Make width and height include padding and border */
  box-sizing: border-box;
  width: 200px;
  padding: 16px;
}

فهم إمكانية الوصول إلى HTML في Canvas

بشكل عام، يتم عرض محتوى HTML الخاص بـ <canvas content="drawable"> لأدوات تسهيل الاستخدام بالطريقة نفسها التي يتم بها عرض DOM العادي، ما يعني أنّه يتم تمثيله في شجرة تسهيل الاستخدام ما لم يتم إخفاؤه صراحةً باستخدام سمات مثل aria-hidden="true" أو inert. ومع ذلك، هناك استثناء رئيسي واحد: عنصر drawable لم يتم إنشاء أو تعديل هندسته المعروضة بعد.

لا يتضمّن عنصر drawable داخل <canvas content="drawable"> هندسة معروضة في الحالات التالية:

  • لم يتم عرضه بعد باستخدام drawElementImage() الذي يحدّث الشكل الهندسي تلقائيًا بشكل تلقائي.
  • لم يتم تعديل الشكل الهندسي بشكل صريح باستخدام updateElementGeometry().

إذا كان عنصر drawable يفتقر إلى الشكل الهندسي المعروض، يتم إرسال عنصر drawable هذا وشجرته الفرعية drawable إلى شجرة تسهيل الاستخدام بدون معلومات الشكل الهندسي. ويُعدّ ذلك مفيدًا، على سبيل المثال، للسماح لبرامج قراءة الشاشة بالوصول إلى المحتوى الدلالي، مع إرسال إشارة إلى أدوات المساعدة المرئية (مثل المكبّرات) لفلترة العناصر غير المرسومة.

يحافظ المثال التالي على دلالات الشكل والصورة والتعليق أثناء رسم الصورة والشكل بشكل منفصل:

<canvas id="canvas" width="400" height="300" content="drawable">
  <figure id="figure">
    <img drawable id="image" src="..." alt="image alt text" />
    <figcaption drawable id="caption">A caption</figcaption>
  </figure>
</canvas>
<script>
  const ctx = canvas.getContext('2d');

  canvas.onpaint = () => {
    ctx.reset();
    // Note: Because `preserveElementGeometry` defaults to false, the
    // `drawElementImage` call automatically updates geometry, ensuring
    // the image and caption have geometry information for accessibility.
    ctx.drawElementImage(image, 0, 0);
    ctx.drawElementImage(caption, 0, 200);
  };
</script>

استراتيجيات احتياطية

تم طرح واجهة HTML-in-Canvas API في الإصدار 157 من Chrome، وقد لا تكون متوافقة مع بعض المتصفّحات. للتأكّد من أنّ الحلّ يعمل بسلاسة للجميع، تحقَّق مما إذا كان المتصفّح يتيح استخدام واجهة برمجة التطبيقات، واستخدِم حلاً بديلاً إذا لزم الأمر:

export function supportsHtmlInCanvas() {
  if (typeof document === 'undefined') return false;
  const canvas = document.createElement('canvas');
  canvas.setAttribute('content', 'drawable');
  return canvas.content === 'drawable';
}

إذا لم تكن واجهة برمجة التطبيقات متاحة، يمكنك اختيار أحد البدائل التالية استنادًا إلى أهداف التنفيذ:

الخطوات التالية