افهم المراحل الأربع لعرض المتصفح لتعرف بالضبط أي خصائص CSS مكلِفة وكيف تعمل will-change في الخفاء.
افتح هذا الدرس في Kodokonيحوّل المتصفح CSS الخاصة بك إلى بكسلات عبر خط أنابيب من أربع مراحل: النمط (حساب القيم)، والتخطيط (الهندسة، ويُعرف أيضًا بإعادة التخطيط)، والرسم (التحويل إلى بكسلات، أي إعادة الرسم) والتركيب (تجميع الطبقات على وحدة معالجة الرسوميات GPU). القاعدة الذهبية: كلما لمست مرحلة أبكر، ازدادت الفاتورة، لأن كل مرحلة لاحقة تُعاد. تغيير width أو top يُطلق التخطيط + الرسم + التركيب. تغيير color أو box-shadow يتجاوز التخطيط لكنه يعيد الرسم. وحدهما transform وopacity يمكن أن يتولاهما خيط التركيب بمفرده: تبقى الحركة سلسة حتى عندما يكون الخيط الرئيسي محجوبًا بواسطة JavaScript.
.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): إعادة تخطيط كاملة في كل تكرار بدلًا من إعادة واحدة للدفعة بأكملها.
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';
});يقسّم التركيب الصفحة إلى طبقات تُرسم كل منها على حدة ثم تجمّعها وحدة معالجة الرسوميات. يطلب will-change: transform ترقية العنصر إلى طبقته الخاصة قبل الحركة، متفاديًا التحويل المكلِف إلى بكسلات في الإطار الأول. ثمة دقيقة في المواصفة كثيرًا ما يُغفَل عنها: يجب أن يُحدث will-change نفس الآثار الجانبية للخاصية التي يُعلن عنها. لذا فإن will-change: transform يُنشئ سياق تراص (stacking context) ويحوّل العنصر إلى كتلة احتواء لأحفاده ذوي position: fixed - تمامًا مثل transform حقيقي. فتلميح أداء بسيط قد يكسر ترتيب التراص لديك أو عناصرك الثابتة.
const panel = document.querySelector('.panel');
panel.addEventListener('pointerenter', () => {
panel.style.willChange = 'transform';
});
panel.addEventListener('transitionend', () => {
panel.style.willChange = 'auto';
});