معرفی نسخه آزمایشی اولیه API HTML-in-Canvas

توماس ناتستاد
Thomas Nattestad

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

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

DOM در مقابل Canvas

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

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

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

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

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

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

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

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

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

  • برنامه‌های بزرگ مبتنی بر بوم: برنامه‌های وب سنگین مانند Google Docs، Miro یا Figma اکنون می‌توانند اجزای رابط کاربری پیچیده برنامه را به صورت بومی در فضاهای کاری مبتنی بر بوم خود رندر کنند، که باعث بهبود دسترسی و کاهش وزن بسته می‌شود.
  • صحنه‌ها و بازی‌های سه‌بعدی: سایت‌های بازاریابی، تجربیات فراگیر WebXR و بازی‌های وب اکنون می‌توانند رابط کاربری وب کاملاً تعاملی را در صحنه‌های سه‌بعدی قرار دهند - مانند یک کتاب سه‌بعدی که از متن DOM واقعی استفاده می‌کند، یا یک ترمینال درون بازی که به صورت بومی از کپی و چسباندن پشتیبانی می‌کند.

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

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

پیش‌نیازها

رابط برنامه‌نویسی کاربردی HTML-in-Canvas در نسخه‌های ۱۴۸ تا ۱۵۰ کروم در حال آزمایش اولیه است. برای آزمایش آن در سایت خود، از کروم کانری ۱۴۹ یا بالاتر با فعال بودن پرچم chrome://flags/#canvas-draw-element استفاده کنید. برای فعال کردن API برای سایر کاربران، در نسخه آزمایشی اولیه ثبت‌نام کنید.

مرحله ۱: تنظیمات اولیه بوم نقاشی

ابتدا، ویژگی 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>

اندازه شبکه بوم را تنظیم کنید

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

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

  • تبدیل ماتریس MVP وب‌جی‌ال به ماتریس 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 را ارائه داده‌اند.

سه.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 همچنین با استفاده از API بافت خود از HTML-in-Canvas پشتیبانی می‌کند:

// 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"> step پاسخ می‌دهد.
  • بافت‌های متحرک : یک بیلبورد سه‌بعدی پویا که یک مداد SVG متحرک را با استفاده از DOM مستقیماً در یک بافت WebGL و بدون نیاز به یک حلقه انیمیشن سفارشی رندر می‌کند.
  • پوشش‌های انکساری : یک لایه تایپوگرافی تعاملی که توسط یک مکان‌نمای سه‌بعدی متحرک تحریف شده است، اما با استفاده از قابلیت یافتن در صفحه، کاملاً قابل انتخاب و جستجو است.

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

محدودیت‌ها

اگرچه این API قدرتمند است، اما چند محدودیت آگاهانه دارد:

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

بازخورد

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

منابع