به‌روزرسانی‌های HTML-in-Canvas: حرکت به سمت یک API وب بهتر

کروم ۱۵۰ و ۱۵۵ چندین تغییر در API مربوط به HTML-in-Canvas ایجاد کرد. این تغییرات به بازخوردهای جامعه و استانداردها با هدف بهبود ارگونومی API پاسخ دادند. این پست وبلاگ، تغییرات و نحوه اطمینان از به‌روز بودن پیاده‌سازی شما با آن‌ها را شرح می‌دهد.

این به‌روزرسانی‌ها شامل تغییر نام ویژگی‌ها و متدها، الزام به پیش‌تخصیص حافظه بافت صریح و به‌روزرسانی مدل‌های همگام‌سازی DOM می‌شود. علاوه بر این، نسخه آزمایشی HTML-in-Canvas Origin اکنون تمدید شده است. در ادامه توضیحی در مورد تغییرات، دلیل آنها و نحوه به‌روزرسانی پایگاه کد شما ارائه شده است.

تغییرات در کروم ۱۵۰

در کروم ۱۵۰، ما امضاهای آرگومان را در سراسر WebGL و WebGPU تغییر دادیم تا افزونگی را از بین ببریم و با قراردادهای گرافیکی مدرن همسو شویم.

وب‌جی‌ال

ما آرگومان‌های اضافی ( level ، srcFormat ، destType ) را از texElementImage2D حذف کردیم (برای جزئیات بیشتر به بحث WebGL مراجعه کنید):

// Old implementation (Chrome < 150)
const level = 0;
const internalFormat = gl.RGBA;
const srcFormat = gl.RGBA;
const destType = gl.UNSIGNED_BYTE;

gl.texElementImage2D(
  gl.TEXTURE_2D,
  level,
  internalFormat,
  srcFormat,
  destType,
  element
);

// New implementation (from Chrome 150)
const internalFormat = gl.RGBA8;
gl.texElementImage2D(gl.TEXTURE_2D, internalFormat, element);

پردازنده گرافیکی وب

ما پارامترهای copyElementImageToTexture در اشیاء sourceDict و destDict استانداردسازی کردیم (به GPUWeb PR#6250 مراجعه کنید):

// Old implementation (Chrome < 150)
GPUQueue.copyElementImageToTexture(
  element,
  width,
  height,
  { texture: texture }
);

// New implementation (from Chrome 150)
const sourceDict = { source: element };
const destDict = {
  destination: { texture: texture },
  width: width,
  height: height
};

GPUQueue.copyElementImageToTexture(sourceDict, destDict);

تغییرات در کروم ۱۵۵

کروم ۱۵۵ بر اساس بازخورد توسعه‌دهندگان، اصلاحات بیشتری را ارائه می‌دهد. ما در حال به‌روزرسانی نام متدها، نیاز به پیش‌تخصیص حافظه بافت صریح، پشتیبانی از زیردرخت‌های پیچیده DOM و جداسازی همگام‌سازی DOM هستیم.

پردازنده گرافیکی وب

متد WebGPU از copyElementImageToTexture به drawElementImageToTexture تغییر نام داده است. همراه با این تغییر نام، مختصات پارامترها ( sourceX ، sourceWidth ) گسترش یافته و مرزهای مقصد در یک شیء size استاندارد ( GPUExtent3D ) گروه‌بندی می‌شوند:

// BEFORE (Chrome 150)
const sourceDict = {
  source: myElement,
  sx: 0,
  sy: 0,
  swidth: 100,
  sheight: 100
};
const destDict = {
  destination: { texture: myTexture },
  width: 300,
  height: 200
};

queue.copyElementImageToTexture(sourceDict, destDict);

// AFTER (Chrome 155+)
const sourceDict = {
  source: myElement,
  sourceX: 0,
  sourceY: 0,
  sourceWidth: 100,
  sourceHeight: 100
};
const destDict = {
  texture: myTexture,
  size: { width: 300, height: 200 }
};

queue.drawElementImageToTexture(sourceDict, destDict);

وب‌جی‌ال

سه جنبه برای تغییرات در WebGL برای HTML-in-Canvas وجود دارد:

  • نام متد از texElementImage2D به texElementSubImage2D تغییر می‌کند.
  • امضای متد تغییر می‌کند؛ مهم‌تر از همه، آرگومان‌های xoffset و yoffset را اضافه می‌کند تا به صورت اختیاری امکان تعیین یک آفست در بافت مقصد که عنصر در آن ترسیم می‌شود را فراهم کند.
  • این روش دیگر به طور خودکار فضای بافر را برای بافت مقصد اختصاص نمی‌دهد؛ فضای ذخیره‌سازی باید قبل از استفاده از API به صراحت اختصاص داده شود. این مشابه رفتار texSubImage2D است.
// 1. Capture natural layout size of the element
const elementImage = canvas.captureElementImage(element);

// 2. Pre-allocate texture backing once (or when element resizes)
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA8,
  Math.ceil(elementImage.width),
  Math.ceil(elementImage.height),
  0,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  null // null reserves VRAM without uploading pixels
);

// 3. Upload DOM into pre-allocated VRAM
gl.texElementSubImage2D(
  gl.TEXTURE_2D,
  0,                  // level
  0,                  // xoffset
  0,                  // yoffset
  element             // pass DOM element
);

ویژگی‌های Canvas و عناصر فرزند قابل ترسیم

ما در حال به‌روزرسانی نحوه آماده‌سازی بوم نقاشی برای رندر HTML هستیم.

تغییر نام layoutsubtree به content="drawable"

برای همسو شدن با قراردادهای نامگذاری ویژگی HTML، ویژگی boolean layoutsubtree در عنصر <canvas> به content="drawable" تغییر نام داده شده است (برای بحث مفصل به شماره 169 WICG مراجعه کنید).

ویژگی drawable برای فرزندان

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

<!-- BEFORE -->
<canvas layoutsubtree>
  <div id="badge">
    <h2>Player Stats</h2>
    <button id="actionBtn">Equip</button>
  </div>
</canvas>

<!-- AFTER (Chrome 155+) -->
<canvas content="drawable">
  <!-- Subtree A (Body and heading) -->
  <div id="badge" drawable>
    <h2>Player Stats</h2>
    <!-- Subtree B (Excluded from Subtree A; drawn and animated independently) -->
    <button id="actionBtn" drawable>Equip</button>
  </div>
</canvas>

یک عنصر با drawable کل زیردرخت خود را به جز فرزندان تو در تو که drawable نیز مشخص می‌کنند، در بر می‌گیرد. این امر به کانتینرهای والد و عناصر فرزند مانند دکمه‌ها اجازه می‌دهد تا در فراخوانی‌های draw جداگانه متحرک‌سازی و ترسیم شوند.

همگام‌سازی تست ضربه

پیش از این، توسعه‌دهندگان مجبور بودند مقدار بازگشتی از متدهای طراحی را دریافت کرده و آن را به element.style.transform منتقل کنند. اکنون متد drawElementImage() مقدار void را برمی‌گرداند و همگام‌سازی به زمینه بستگی دارد.

زمینه‌های دوبعدی: همگام‌سازی خودکار

در بوم دوبعدی، مرورگر به‌طور خودکار محدوده‌های تست ضربه DOM و حلقه‌های فوکوس صفحه‌خوان را با مختصات ترسیم‌شده همگام‌سازی می‌کند. می‌توانید تمام لوله‌کشی‌های تبدیل دستی را حذف کنید:

// BEFORE
const transform = ctx.drawElementImage(element, x, y);
element.style.transform = transform.toString();

// AFTER (Chrome 155+)
ctx.drawElementImage(element, x, y);
// No style update needed: position & hit-testing sync automatically

برای مدیریت دستی هندسه در حالت دوبعدی، { preserveElementGeometry: true } را به drawElementImage() ارسال کنید و تبدیل‌ها را با استفاده از canvas.updateElementGeometry() اعمال کنید.

زمینه‌های سه‌بعدی: تابع updateElementGeometry()

برای همگام‌سازی DOM در زمینه‌های WebGL یا WebGPU، یک تبدیل DOMMatrix بسازید و آن را به canvas.updateElementGeometry() ارسال کنید:

canvas.addEventListener('paint', () => {
  // 1. Upload element snapshot to GPU texture
  device.queue.drawElementImageToTexture({ source: element }, { texture });

  // 2. Render 3D scene...
  drawScene();

  // 3. Construct transform matrix as needed
  const canvasTransform = new DOMMatrix().translate(x, y);

  // 4. Update DOM hit-testing and accessibility bounding boxes
  canvas.updateElementGeometry(element, { canvasTransform });
});

سازگاری بین نسخه‌ای

اگر برنامه شما باید در طول دوره انتقال از کاربران در چندین نسخه Chrome پشتیبانی کند، وجود متدهای به‌روز شده را در نمونه‌های اولیه آنها بررسی کنید و پیاده‌سازی خود را بر اساس آن تنظیم کنید.

مطمئن شوید که ویژگی‌های عنصر <canvas> را برای هر نسخه مرورگر به درستی تنظیم کرده‌اید:

// ============================================================================
//  Cross-version canvas markup
// ============================================================================
if (!canvas.hasAttribute('content') && !canvas.hasAttribute('layoutsubtree')) {
  if ('content' in HTMLCanvasElement.prototype) {
    // Chrome 155+ (New standardized attribute)
    canvas.setAttribute('content', 'drawable');
  } else {
    // Chrome < 155 (Legacy boolean attribute)
    canvas.setAttribute('layoutsubtree', '');
  }
}

برای زمینه دوبعدی، مقدار بازگشتی تابع drawElementImage() را بررسی کنید و ویژگی‌های نمونه اولیه بوم را برای پشتیبانی از هر دو نسخه بررسی کنید:

// ============================================================================
// Cross-version 2D drawing & synchronization
// ============================================================================
function draw2DElement(ctx, element, x, y) {
  // Execute the 2D draw call
  const result = ctx.drawElementImage(element, x, y);

  if (result !== undefined && typeof result?.toString === 'function') {
    // Chrome < 155: drawElementImage returned a DOMMatrix
    // Requires manual transform piping to align DOM hit-test bounds:
    element.style.transform = result.toString();
  } else {
    // Chrome 155+: drawElementImage returns void / undefined
    // The browser automatically aligns DOM positioning, clicks, and a11y bounds!
    // If you previously had element.style.transform logic here, clear it:
    if (element.style.transform) {
      element.style.transform = "";
    }
  }
}

برای WebGPU، از امضا و نام متد سازگار استفاده کنید:

// ============================================================================
// WebGPU cross-version support
// ============================================================================
if ('drawElementImageToTexture' in GPUQueue.prototype) {
  // Chrome 155+ (Latest implementation)
  queue.drawElementImageToTexture(
    { source: element, sourceX: 0, sourceY: 0, sourceWidth: w, sourceHeight: h },
    { texture: myTexture, size: { width: w, height: h } }
  );
} else if (typeof queue.copyElementImageToTexture === "function") {
  // Chrome 150 - 154: Try the Chrome 150+ dictionary signature first,
  // then fall back to the Chrome < 150 signature
  try {
    queue.copyElementImageToTexture(
      { source: element },
      { destination: { texture: myTexture }, width: w, height: h }
    );
  } catch (err) {
    // Chrome < 150
    queue.copyElementImageToTexture(element, { texture: myTexture });
  }
}

حتماً سازگاری مرورگر را برای WebGL بررسی کنید:

// ============================================================================
// WebGL cross-version support
// ============================================================================
if ('texElementSubImage2D' in WebGL2RenderingContext.prototype) {
  // Chrome 155+ (Explicit pre-allocation + sub-image)
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element);
} else if (gl.texElementImage2D && gl.texElementImage2D.length === 3) {
  // Chrome 150-154 (3-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, element);
} else if (gl.texElementImage2D) {
  // Chrome < 150 (6-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, element);
}

تمدید نسخه آزمایشی Origin

نسخه آزمایشی HTML-in-Canvas Origin از طریق کروم ۱۶۰ تمدید شده است. برای ادامه ارائه ویژگی HTML-in-Canvas به کاربران خود، حتماً توکن آزمایشی origin خود را تمدید کنید. برای جزئیات بیشتر به مقاله نسخه آزمایشی Origin مراجعه کنید.

بازخورد خود را به اشتراک بگذارید

با ثبت مشکل در مخزن گیت‌هاب WICG ، به ما اطلاع دهید که این به‌روزرسانی‌ها چگونه در برنامه‌های شما کار می‌کنند. برای اطلاع از هرگونه اخبار HTML-in-Canvas، به خبرنامه توسعه‌دهندگان بپیوندید.