معرفی آزمایش معرفی ویژگی جدید HTML-in-Canvas API

Thomas Nattestad
Thomas Nattestad

سال‌ها است که توسعه‌دهندگان وب هنگام ساختن برنامه‌های دیداری پیچیده و بسیار تعاملی در وب باید انتخاب معماری دشواری داشته باشند. آیا باید به DOM به‌دلیل ویژگی‌های معنایی غنی آن تکیه کنند یا برای عملکرد گرافیکی سطح پایین مستقیماً در عنصر <canvas> رندر کنند؟

با میانای برنامه‌سازی کاربردی HTML-in-Canvas آزمایشی جدید—که اکنون در آزمایش مبدأ دردسترس است—دیگر لازم نیست انتخاب کنید. این میانای برنامه‌سازی کاربردی به شما امکان می‌دهد محتوای DOM را مستقیماً در بوم ۲ بعدی یا بافت WebGL/WebGPU بکشید و درعین‌حال تعاملی بودن، دسترس‌پذیر بودن، و متصل بودن رابط کاربری به ویژگی‌های مرورگر دلخواهتان را حفظ کنید. با ترکیب HTML با پردازش گرافیکی سطح پایین، می‌توانید تجربه‌هایی ایجاد کنید که قبلاً غیرممکن بود.

‫DOM دربرابر Canvas

برای درک قدرت این API جدید، بهتر است به نقاط قوت نسبی DOM و Canvas نگاهی بیندازیم.

‫DOM عنصر اصلی واسط کاربر وب است. این کتابخانه بااستفاده از محتوای درک‌شده معنایی، راه‌حل‌های چیدمان نوشتار را به‌صورت آماده ارائه می‌دهد تا بتوانید میاناهای غنی ایجاد کنید. این کار به کاربران امکان می‌دهد عملیات رایج را در صفحات وب به‌طور یکپارچه انجام دهند—کارهایی که اغلب آن‌ها را بدیهی می‌دانیم، مثل برجسته کردن نوشتار برای کپی کردن، یا کلیک راست روی تصویر برای ذخیره کردن آن. «مدل شیء سند» همچنین با ویژگی‌های ضروری مرورگر ادغام می‌شود: ابزارهای دسترس‌پذیری، ترجمه، یافتن در صفحه، حالت خواننده، افزونه‌ها، حالت تاریک، بزرگ‌نمایی مرورگر، و تکمیل خودکار.

از سوی دیگر، Canvas (و WebGL/WebGPU) امکان دسترسی سطح پایین برای هدایت شبکه‌ای از پیکسل‌ها را برای گرافیک‌های بسیار پیشرفته ۲ بعدی و ۳ بعدی فراهم می‌کند. بازی‌ها و برنامه‌های وب پیچیده (مثل «سندنگار Google» یا Figma) به این دسترسی سطح پایین و با عملکرد بالا نیاز دارند. ازآنجایی‌که بوم اساساً شبکه‌ای از پیکسل‌ها است، پشتیبانی از ویژگی‌هایی مثل نوشتار واکنش‌گرا قبلاً به منطق پیچیده واسط کاربر سفارشی نیاز داشت و اندازه دسته‌بندی شما را به‌شدت افزایش می‌داد. نکته مهم این است که وقتی واسط کاربر در یک شبکه پیکسل بوم ایستا گیر می‌افتد، همه ویژگی‌های قدرتمند مرورگر که در DOM ادغام شده‌اند کاملاً ازکار می‌افتند.

مزایای آوردن DOM به Canvas

«میانای برنامه کاربردی HTML-in-Canvas» پلی است که بهترین‌های هر دو جهان را به شما ارائه می‌دهد. با قرار دادن HTML در عنصر <canvas> و همگام‌سازی تبدیل آن، مطمئن می‌شوید که محتوا کاملاً تعاملی باقی می‌ماند و همه یکپارچه‌سازی‌های مرورگر به‌طور خودکار عمل می‌کنند.

با اجازه دادن به DOM برای مدیریت کردن واسط کاربر در عنصر <canvas>، این موارد را دریافت می‌کنید:

  • چیدمان و قالب‌بندی نوشتار: چیدمان و قالب‌بندی نوشتار ساده‌شده، ازجمله نوشتار چندخطی یا دوزبانه با سبک‌های CSS اعمال‌شده.
  • کنترل‌های فرم: کنترل‌های فرم بیانی و آسان‌تر برای استفاده با گزینه‌های سفارشی‌سازی گسترده.
  • انتخاب نوشتار، کپی/جای‌گذاری، و کلیک راست: کاربران می‌توانند نوشتار را در صحنه‌های سه‌بعدی شما برجسته کنند یا به‌صورت بومی از منوهای زمینه‌ای کلیک راست استفاده کنند.
  • دسترس‌پذیری: محتوای پردازش‌شده در داخل بوم به درخت دسترس‌پذیری ارائه می‌شود. سیستم‌های دسترس‌پذیری می‌توانند «میانای کاربر» را مانند HTML معمولی تجزیه کنند و آن را دراختیار سیستم‌هایی مثل صفحه‌خوان‌ها قرار دهند.
  • یافتن در صفحه: کاربران می‌توانند از «یافتن در صفحه» (Ctrl/Cmd+F) برای جستجوی نوشتار استفاده کنند و مرورگر آن را مستقیماً در بافت‌های WebGL شما برجسته می‌کند.
  • نمایه‌پذیری و واسط عامل هوش مصنوعی: خزنده وب و عامل‌های هوش مصنوعی می‌توانند نوشتار پردازش‌شده در صحنه‌های دوبعدی و سه‌بعدی شما را به‌صورت یکپارچه نمایه‌گذاری و بخوانند.
  • یکپارچه‌سازی افزونه: افزونه‌های مرورگر به‌صورت بومی کار می‌کنند. برای مثال، افزونه جایگزینی نوشتار به‌طور خودکار نوشتار پردازش‌شده در مش‌های سه‌بعدی را به‌روز می‌کند.
  • ادغام DevTools: می‌توانید محتوای بوم نقاشی خود، ازجمله عناصر رابط کاربری WebGL/WebGPU را مستقیماً در Chrome DevTools بازرسی کنید. سبک CSS را در بازرس تغییر دهید و ببینید که بافت سه‌بعدی بلافاصله به‌روز می‌شود!

موارد استفاده سطح بالا

این API پتانسیل فوق‌العاده‌ای را در چندین حوزه باز می‌کند:

  • برنامه‌های مبتنی بر بوم بزرگ: برنامه‌های وب سنگین‌وزن اکنون می‌توانند مؤلفه‌های پیچیده واسط کاربر برنامه را به‌صورت بومی در فضاهای کاری مبتنی بر بوم خود پرداز کنند و ازاین‌طریق دسترسی‌پذیری را بهبود بخشند و وزن دسته را کاهش دهند.
  • صحنه‌ها و بازی‌های سه‌بعدی: سایت‌های بازاریابی، تجربه‌های فراگیر WebXR، و بازی‌های وب اکنون می‌توانند رابط کاربری وب کاملاً تعاملی را در صحنه‌های سه‌بعدی قرار دهند—مانند کتاب سه‌بعدی که از نوشتار DOM واقعی استفاده می‌کند، یا پایانه درون‌بازی که به‌طور بومی از کپی و جای‌گذاری پشتیبانی می‌کند.

نحوه استفاده از API

استفاده از API در سه مرحله انجام می‌شود: تنظیم بوم، رندر کردن در بوم، و به‌روزرسانی تبدیل CSS به‌طوری‌که مرورگر بداند عنصر در کجای صفحه قرار دارد.

پیش‌نیازها

«میانای برنامه‌سازی کاربردی HTML در بوم نقاشی» در آزمایش ویژگی‌های جدید در Chrome نسخه ۱۴۸ تا ۱۵۰ دردسترس است. برای آزمایش آن در سایتتان، از Chrome Canary نسخه ۱۴۹ یا بالاتر با پرچم chrome://flags/#canvas-draw-element فعال استفاده کنید. برای فعال کردن API برای کاربران دیگر، برای آزمایش مبدأ ثبت‌نام کنید.

مرحله ۱: راه‌اندازی اولیه «بوم»

ابتدا مشخصه layoutsubtree را به برچسب <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>

اندازه جدول بوم

برای جلوگیری از تار شدن محتوای پردازش‌شده، مطمئن شوید که اندازه شبکه بوم نقاشی با عامل مقیاس دستگاه مطابقت داشته باشد.

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

مرحله ۲: پردازش

برای بافت ۲ بعدی، از روش 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 }
  );
};

مرحله ۳: به‌روزرسانی تبدیل CSS

اکنون که عنصر را در بوم رندر کرده‌اید، باید مرورگر را از مکان آن مطلع کنید. این کار باعث همگام‌سازی فضایی بین بوم و چیدمان 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، مکان عنصر روی صفحه به نحوه استفاده بافت برونداد توسط کد سایه‌زن بستگی دارد و نمی‌توان آن را از بافت پردازنده بوم استنباط کرد. بااین‌حال، اگر برنامه سایه‌زن شما از مدل نمای پیش‌بینی معمولی برای رسم بافت استفاده می‌کند، می‌توانید از تابع جدید element.getElementTransform() برای محاسبه تبدیل استفاده کنید که می‌تواند به همان روش مقدار برگشتی از drawElementImage() استفاده شود. برای تسهیل این امر، باید کارهای زیر را انجام دهید:

  • تبدیل ماتریس MVP ‏WebGL به ماتریس DOM.
  • عنصر HTML را نرمال‌سازی کنید. اندازه عناصر HTML برحسب پیکسل است (برای مثال، عرض ۲۰۰ پیکسل). بااین‌حال، WebGL معمولاً با اشیا به‌عنوان «مربع‌های واحد» رفتار می‌کند، برای مثال، از ۰ تا ۱. اگر عادی‌سازی نکنید، دکمه ۲۰۰ پیکسل شما ۲۰۰ برابر بزرگ‌تر به‌نظر می‌رسد.
  • به ناحیه نمایش بوم نگاشت کنید. این مرحله فاز «مقیاس‌بندی مجدد» است. این واحد-فضای ریاضی را دوباره گسترش می‌دهد تا با ابعاد پیکسل واقعی عنصر <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();
  }
}

پشتیبانی از کتابخانه و چارچوب

برخی‌از کتابخانه‌های محبوب ازقبل از ویژگی 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 است، ترجمه داخلی فوراً کار می‌کند و کارگزاران هوش مصنوعی می‌توانند نوشتار را با پیچیدگی کمتری استخراج کنند.
  • واسط‌های کاربر سه‌بعدی تعاملی: لغزنده ژله‌ای WebGPU که نور را براساس مدل سه‌بعدی زیرین می‌شکند و درعین‌حال به ویژگی‌های گام HTML استاندارد <input type="range"> پاسخ می‌دهد.
  • بافت‌های پویانمایی‌شده: تابلوی اعلانات سه‌بعدی پویا که بااستفاده از DOM مستقیماً در بافت WebGL مداد SVG پویانمایی‌شده‌ای را بدون نیاز به حلقه پویانمایی سفارشی رندر می‌کند.
  • رونهادهای انکساری: لایه حروف‌چینی تعاملی که با مکان‌نمای سه‌بعدی متحرک تحریف شده است، اما بااستفاده از «پیدا کردن در صفحه» کاملاً قابل‌انتخاب و جستجو است.

مجموعه نمایش‌های فنی ایجادشده توسط انجمن را بررسی کنید. اگر می‌خواهید نسخه نمایشی HTML-in-Canvas شما در این مجموعه نمایش داده شود، درخواست کشیدن برای افزودن آن ایجاد کنید.

محدودیت‌ها

این «میانای برنامه‌سازی کاربردی» قدرتمند است، اما چند محدودیت آگاهانه دارد:

  • محتوای بین مبدأ: به‌دلایل امنیتی و حریم خصوصی، این API با محتوای iframe بین مبدأ کار نمی‌کند.
  • پیمایش رشته اصلی: HTML درون بوم با جاوا اسکریپت کشیده می‌شود، که یعنی پیمایش و پویانمایی‌ها نمی‌توانند مستقل از جاوا اسکریپت به‌روز شوند، همان‌طور که در خارج از بوم می‌توانند. توسعه‌دهندگان باید ویژگی‌های عملکردی قرار دادن محتوای پیمایشی در داخل بوم در مقابل پیمایش کل بوم را به‌دقت درنظر بگیرند.

بازخورد

اگر درحال آزمایش کردن با HTML-in-Canvas API هستید، می‌خواهیم نظرتان را بدانیم! می‌توانید برای آزمایش مبدأ ثبت‌نام کنید تا ویژگی را در سایتتان فعال کنید و به ما کمک کنید طراحی «میانای برنامه‌سازی کاربردی» را شکل دهیم. همچنین می‌توانید برای ارائه بازخورد، مشکلی را ثبت کنید.

منابع