Kodokon kodokon.com

خط أنابيب العرض: إعادة التخطيط، إعادة الرسم، والتركيب

افهم المراحل الأربع لعرض المتصفح لتعرف بالضبط أي خصائص CSS مكلِفة وكيف تعمل will-change في الخفاء.

11 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

يحوّل المتصفح CSS الخاصة بك إلى بكسلات عبر خط أنابيب من أربع مراحل: النمط (حساب القيم)، والتخطيط (الهندسة، ويُعرف أيضًا بإعادة التخطيط)، والرسم (التحويل إلى بكسلات، أي إعادة الرسم) والتركيب (تجميع الطبقات على وحدة معالجة الرسوميات GPU). القاعدة الذهبية: كلما لمست مرحلة أبكر، ازدادت الفاتورة، لأن كل مرحلة لاحقة تُعاد. تغيير width أو top يُطلق التخطيط + الرسم + التركيب. تغيير color أو box-shadow يتجاوز التخطيط لكنه يعيد الرسم. وحدهما transform وopacity يمكن أن يتولاهما خيط التركيب بمفرده: تبقى الحركة سلسة حتى عندما يكون الخيط الرئيسي محجوبًا بواسطة JavaScript.

CSS
.slide-bad {
  position: absolute;
  transition: left 0.3s ease;
}
.slide-bad:hover { left: 120px; }

.slide-good {
  transition: transform 0.3s ease;
}
.slide-good:hover { transform: translateX(120px); }
حركتان متطابقتان بصريًا: الأولى تفرض إعادة تخطيط في كل إطار، والثانية تبقى على المُركِّب.

المتصفح كسول: فهو يجمّع عمليات كتابة النمط ولا يعيد حساب التخطيط إلا عند التحديث التالي. لكن بعض عمليات القراءة في JavaScript - مثل offsetHeight وgetBoundingClientRect() وscrollTop وgetComputedStyle() على خاصية هندسية - تتطلب قيمًا محدَّثة. فإن كانت هناك عملية كتابة معلَّقة، يجب على المتصفح أن يعيد الحساب فورًا وبشكل متزامن: هذه إعادة تخطيط قسرية. والتناوب بين القراءات والكتابات داخل حلقة يُنتج ضربًا للتخطيط (layout thrashing): إعادة تخطيط كاملة في كل تكرار بدلًا من إعادة واحدة للدفعة بأكملها.

JAVASCRIPT
const items = document.querySelectorAll('.item');

items.forEach((item) => {
  const h = item.offsetHeight;
  item.style.height = h * 2 + 'px';
});

const heights = [...items].map((i) => i.offsetHeight);
items.forEach((item, index) => {
  item.style.height = heights[index] * 2 + 'px';
});
النسخة 1: إعادة تخطيط قسرية لكل عنصر. النسخة 2: كل القراءات أولًا ثم كل الكتابات - إعادة تخطيط واحدة.

يقسّم التركيب الصفحة إلى طبقات تُرسم كل منها على حدة ثم تجمّعها وحدة معالجة الرسوميات. يطلب will-change: transform ترقية العنصر إلى طبقته الخاصة قبل الحركة، متفاديًا التحويل المكلِف إلى بكسلات في الإطار الأول. ثمة دقيقة في المواصفة كثيرًا ما يُغفَل عنها: يجب أن يُحدث will-change نفس الآثار الجانبية للخاصية التي يُعلن عنها. لذا فإن will-change: transform يُنشئ سياق تراص (stacking context) ويحوّل العنصر إلى كتلة احتواء لأحفاده ذوي position: fixed - تمامًا مثل transform حقيقي. فتلميح أداء بسيط قد يكسر ترتيب التراص لديك أو عناصرك الثابتة.

JAVASCRIPT
const panel = document.querySelector('.panel');

panel.addEventListener('pointerenter', () => {
  panel.style.willChange = 'transform';
});

panel.addEventListener('transitionend', () => {
  panel.style.willChange = 'auto';
});
النمط الذي توصي به المواصفة: الترقية قُبيل الحركة، والتحرير بُعيدها مباشرة.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. أي حركة يمكن أن يتولاها خيط التركيب بالكامل، دون إعادة تخطيط أو إعادة رسم؟
    • height من 0 إلى 200px
    • margin-left من 0 إلى 100px
    • transform: translateX(100px)
    • box-shadow عند التمرير فوق العنصر
  2. لماذا تكون قراءة offsetHeight داخل حلقة تعدّل الأنماط أيضًا مكلِفة إلى هذا الحد؟
    • كل قراءة تفرض إعادة تخطيط متزامنة إن كانت هناك كتابة معلَّقة
    • offsetHeight يُطلق إعادة رسم على وحدة معالجة الرسوميات عند كل وصول
    • الخاصية لا يخزّنها محرك JavaScript في الذاكرة المؤقتة أبدًا
  3. أي أثر جانبي يشترك فيه will-change: transform مع transform حقيقي؟
    • يعطّل انتقالات CSS للعنصر
    • يُنشئ سياق تراص وكتلة احتواء لعناصر position: fixed
    • يفرض على العنصر display: block
    • يضاعف دقة التحويل إلى بكسلات