ทำความเข้าใจสี่ระยะของการเรนเดอร์ในเบราว์เซอร์ เพื่อให้คุณรู้แน่ชัดว่าคุณสมบัติ CSS ตัวใดมีต้นทุนสูง และ will-change ทำงานเบื้องหลังอย่างไร
เปิดบทเรียนนี้ใน Kodokonเบราว์เซอร์เปลี่ยน CSS ของคุณให้กลายเป็นพิกเซลผ่านไปป์ไลน์สี่ระยะ: Style (คำนวณค่า), Layout (เรขาคณิต หรือที่รู้จักกันในชื่อ reflow), Paint (แรสเตอร์ไรซ์ให้เป็นพิกเซล หรือ repaint) และ Composite (ประกอบเลเยอร์ต่าง ๆ บน GPU) กฎทองคือ ยิ่งคุณแตะระยะที่อยู่ต้น ๆ มากเท่าไร ต้นทุนก็ยิ่งสูงเท่านั้น เพราะทุกระยะที่ตามมาต้องถูกประมวลผลใหม่ทั้งหมด การเปลี่ยน width หรือ top จะกระตุ้น layout + paint + composite การเปลี่ยน color หรือ box-shadow จะข้าม layout แต่ยังต้อง repaint มีเพียง 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); }เบราว์เซอร์นั้นขี้เกียจ: มันจะรวบรวมการเขียนสไตล์ของคุณเป็นชุด และคำนวณ layout ใหม่เฉพาะตอนรีเฟรชครั้งถัดไปเท่านั้น แต่การอ่านค่าบางอย่างใน JavaScript เช่น offsetHeight, getBoundingClientRect(), scrollTop, getComputedStyle() บนคุณสมบัติเชิงเรขาคณิต ต้องการค่าที่เป็นปัจจุบัน หากมีการเขียนที่ค้างอยู่ เบราว์เซอร์ต้องคำนวณใหม่ ในทันทีและแบบซิงโครนัส: นั่นคือ forced reflow การสลับกันอ่านและเขียนภายในลูปจะทำให้เกิด layout thrashing: เกิด reflow เต็มรูปแบบหนึ่งครั้งต่อหนึ่งรอบการวนซ้ำ แทนที่จะเกิดเพียงครั้งเดียวสำหรับทั้งชุด
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';
});การทำ compositing จะแบ่งหน้าเว็บออกเป็นเลเยอร์ที่ถูกแรสเตอร์ไรซ์แยกกันและประกอบเข้าด้วยกันโดย GPU will-change: transform เป็นการร้องขอให้เอลิเมนต์ถูกเลื่อนขึ้นไปเป็นเลเยอร์ของตัวเอง ก่อน ที่แอนิเมชันจะเริ่ม จึงหลีกเลี่ยงการแรสเตอร์ไรซ์ที่มีต้นทุนสูงในเฟรมแรก มีรายละเอียดปลีกย่อยในสเปกที่มักถูกมองข้าม: will-change ต้องสร้างผลข้างเคียงแบบเดียวกับคุณสมบัติที่มันประกาศไว้ ดังนั้น will-change: transform จึงสร้าง stacking context และทำให้เอลิเมนต์กลายเป็น containing block สำหรับลูกหลานที่เป็น position: fixed เหมือนกับ transform ของจริงทุกประการ คำใบ้ด้านประสิทธิภาพง่าย ๆ จึงอาจทำให้ลำดับการซ้อน (stacking order) หรือเอลิเมนต์ fixed ของคุณเสียหายได้
const panel = document.querySelector('.panel');
panel.addEventListener('pointerenter', () => {
panel.style.willChange = 'transform';
});
panel.addEventListener('transitionend', () => {
panel.style.willChange = 'auto';
});