เลย์เอาต์คือส่วนที่เบราว์เซอร์จะคำนวณข้อมูลเชิงเรขาคณิตขององค์ประกอบ เช่น ขนาดและตําแหน่งในหน้า องค์ประกอบแต่ละรายการจะมีข้อมูลการปรับขนาดที่ชัดเจนหรือไม่ชัดเจนโดยอิงตาม CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบหลัก กระบวนการนี้เรียกว่า "เลย์เอาต์" ใน Chrome
เลย์เอาต์คือส่วนที่เบราว์เซอร์จะคำนวณข้อมูลเชิงเรขาคณิตขององค์ประกอบ เช่น ขนาดและตําแหน่งในหน้า องค์ประกอบแต่ละรายการจะมีข้อมูลการปรับขนาดที่ชัดเจนหรือไม่ชัดเจนโดยอิงตาม CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบหลัก กระบวนการนี้เรียกว่าเลย์เอาต์ใน Chrome (และเบราว์เซอร์ที่มาจาก Chrome เช่น Edge) และ Safari ใน Firefox เรียกว่า "การจัดเรียงใหม่" แต่กระบวนการจะเหมือนกัน
เช่นเดียวกับการคำนวณสไตล์ ข้อกังวลทันทีเกี่ยวกับต้นทุนเลย์เอาต์มีดังนี้
- จํานวนองค์ประกอบที่ต้องจัดเลย์เอาต์ ซึ่งเป็นผลพลอยได้จากขนาด DOM ของหน้า
- ความซับซ้อนของเลย์เอาต์เหล่านั้น
สรุป
- เลย์เอาต์มีผลโดยตรงต่อเวลาในการตอบสนองของการโต้ตอบ
- โดยทั่วไปแล้ว เลย์เอาต์จะมีขอบเขตระดับทั้งเอกสาร
- จำนวนองค์ประกอบ DOM จะส่งผลต่อประสิทธิภาพ คุณจึงควรหลีกเลี่ยงการทริกเกอร์เลย์เอาต์เมื่อเป็นไปได้
- หลีกเลี่ยงเลย์เอาต์แบบซิงค์แบบบังคับและเลย์เอาต์ที่ทำงานหนักเกินไป ให้อ่านค่าสไตล์แล้วทําการเปลี่ยนแปลงสไตล์
ผลกระทบของเลย์เอาต์ต่อเวลาในการตอบสนองของการโต้ตอบ
เมื่อผู้ใช้โต้ตอบกับหน้าเว็บ การโต้ตอบเหล่านั้นควรรวดเร็วที่สุด ระยะเวลาที่ใช้ในการทำการโต้ตอบจนเสร็จ (สิ้นสุดเมื่อเบราว์เซอร์แสดงเฟรมถัดไปเพื่อแสดงผลลัพธ์ของการโต้ตอบ) เรียกว่าเวลาในการตอบสนองของการโต้ตอบ นี่คือแง่มุมหนึ่งของประสิทธิภาพหน้าเว็บที่เมตริก Interaction to Next Paint จะวัด
ระยะเวลาที่เบราว์เซอร์ใช้ในการแสดงเฟรมถัดไปเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้เรียกว่าความล่าช้าในการแสดงผลของการโต้ตอบ เป้าหมายของการโต้ตอบคือการให้ฟีดแบ็กภาพเพื่อแจ้งให้ผู้ใช้ทราบว่ามีบางอย่างเกิดขึ้น และการอัปเดตภาพอาจเกี่ยวข้องกับงานเลย์เอาต์บางส่วนเพื่อให้บรรลุเป้าหมายดังกล่าว
หากต้องการให้ INP ของเว็บไซต์ต่ำที่สุดเท่าที่จะเป็นไปได้ คุณควรหลีกเลี่ยงการจัดวางเมื่อทำได้ หากไม่สามารถหลีกเลี่ยงเลย์เอาต์ได้ทั้งหมด ก็ควรจำกัดเลย์เอาต์นั้นเพื่อให้เบราว์เซอร์นำเสนอเฟรมถัดไปได้อย่างรวดเร็ว
หลีกเลี่ยงเลย์เอาต์หากเป็นไปได้
เมื่อคุณเปลี่ยนสไตล์ เบราว์เซอร์จะตรวจสอบว่าการเปลี่ยนแปลงใดๆ จำเป็นต้องคำนวณเลย์เอาต์หรือไม่ และจำเป็นต้องอัปเดตต้นไม้การแสดงผลหรือไม่ การเปลี่ยนแปลง "คุณสมบัติเชิงเรขาคณิต" เช่น ความกว้าง ความสูง ซ้าย หรือบน ล้วนต้องใช้เลย์เอาต์
.box {
width: 20px;
height: 20px;
}
/**
* Changing width and height
* triggers layout.
*/
.box--expanded {
width: 200px;
height: 350px;
}
เกือบทุกครั้งเลยที่เลย์เอาต์จะมีขอบเขตระดับทั้งเอกสาร หากมีองค์ประกอบจำนวนมาก ระบบจะใช้เวลานานในการหาตําแหน่งและขนาดขององค์ประกอบทั้งหมด
หากไม่สามารถหลีกเลี่ยงการจัดวางได้ สิ่งสำคัญคือการใช้ Chrome DevTools อีกครั้งเพื่อดูระยะเวลาการใช้งาน และพิจารณาว่าการจัดวางเป็นสาเหตุของจุดคอขวดหรือไม่ ก่อนอื่น ให้เปิดเครื่องมือสำหรับนักพัฒนาเว็บ ไปที่แท็บไทม์ไลน์ คลิกบันทึก แล้วโต้ตอบกับเว็บไซต์ เมื่อหยุดบันทึก คุณจะเห็นรายละเอียดประสิทธิภาพของเว็บไซต์ ดังนี้
เมื่อเจาะลึกการติดตามในตัวอย่างข้างต้น เราพบว่าแต่ละเฟรมใช้เวลากว่า 28 มิลลิวินาทีในเลย์เอาต์ ซึ่งสูงเกินไปเมื่อเรามีเวลา 16 มิลลิวินาทีในการแสดงเฟรมบนหน้าจอในภาพเคลื่อนไหว นอกจากนี้ คุณยังเห็นว่าเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะบอกขนาดของต้นไม้ (องค์ประกอบ 1,618 รายการในกรณีนี้) และจํานวนโหนดที่จําเป็นต้องจัดวาง (5 รายการในกรณีนี้)
โปรดทราบว่าคําแนะนําทั่วไปในที่นี้คือหลีกเลี่ยงเลย์เอาต์ทุกครั้งที่เป็นไปได้ แต่บางครั้งก็หลีกเลี่ยงไม่ได้ ในกรณีที่หลีกเลี่ยงเลย์เอาต์ไม่ได้ โปรดทราบว่าต้นทุนของเลย์เอาต์มีความเกี่ยวข้องกับขนาดของ DOM แม้ว่าความสัมพันธ์ระหว่างทั้งสองจะไม่ได้เชื่อมโยงกันอย่างเหนียวแน่น แต่โดยทั่วไปแล้ว DOM ที่มีขนาดใหญ่กว่าจะมีต้นทุนในการจัดวางที่สูงกว่า
หลีกเลี่ยงเลย์เอาต์แบบซิงค์ที่บังคับ
การส่งเฟรมไปยังหน้าจอมีลําดับดังนี้
โดยระบบจะเรียกใช้ JavaScript ก่อน จากนั้นจะคำนวณสไตล์ แล้วจัดวาง แต่คุณสามารถบังคับให้เบราว์เซอร์ทำเลย์เอาต์ก่อนหน้านั้นด้วย JavaScript ได้ วิธีนี้เรียกว่าเลย์เอาต์แบบซิงโครนัสที่บังคับให้
สิ่งแรกที่ควรทราบคือเมื่อ JavaScript ทำงาน ระบบจะทราบค่าเลย์เอาต์เก่าทั้งหมดจากเฟรมก่อนหน้าและพร้อมให้คุณค้นหา ตัวอย่างเช่น หากต้องการเขียนความสูงขององค์ประกอบ (สมมติว่าชื่อ "กล่อง") ที่จุดเริ่มต้นของเฟรม คุณอาจเขียนโค้ดประมาณนี้
// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);
function logBoxHeight () {
// Gets the height of the box in pixels and logs it out:
console.log(box.offsetHeight);
}
ปัญหาจะเกิดขึ้นหากคุณเปลี่ยนรูปแบบของกล่องก่อนขอความสูงของกล่อง
function logBoxHeight () {
box.classList.add('super-big');
// Gets the height of the box in pixels and logs it out:
console.log(box.offsetHeight);
}
ตอนนี้ หากต้องการตอบคำถามเรื่องความสูง เบราว์เซอร์ต้องก่อนอื่นใช้การเปลี่ยนแปลงสไตล์ (เนื่องจากมีการเพิ่มคลาส super-big
) และจากนั้นเรียกใช้เลย์เอาต์ แล้วจะสามารถคืนค่าความสูงที่ถูกต้องได้ ซึ่งไม่จำเป็นและอาจทำให้เสียค่าใช้จ่าย
ด้วยเหตุนี้ คุณจึงควรอ่านสไตล์เป็นกลุ่มและดำเนินการก่อนเสมอ (ซึ่งเบราว์เซอร์จะใช้ค่าเลย์เอาต์ของเฟรมก่อนหน้าได้) จากนั้นจึงทำการเขียน
การดำเนินการอย่างถูกต้อง ฟังก์ชันข้างต้นอาจมีลักษณะดังนี้
function logBoxHeight () {
// Gets the height of the box in pixels and logs it out:
console.log(box.offsetHeight);
box.classList.add('super-big');
}
ส่วนใหญ่แล้วคุณไม่จำเป็นต้องใช้สไตล์แล้วค้นหาค่า การใช้ค่าของเฟรมสุดท้ายก็เพียงพอแล้ว การเรียกใช้การคำนวณสไตล์และเลย์เอาต์พร้อมกันและเร็วกว่าที่เบราว์เซอร์ต้องการอาจเป็นจุดคอขวดที่อาจเกิดขึ้น และไม่ใช่สิ่งที่คุณมักต้องการทำ
หลีกเลี่ยงการข้ามเลย์เอาต์
มีวิธีที่จะทำให้เลย์เอาต์แบบบังคับใช้แบบซิงค์แย่ลงกว่าเดิมได้ ซึ่งก็คือการโพสต์หลายรายการติดต่อกันอย่างรวดเร็ว ดูโค้ดนี้
function resizeAllParagraphsToMatchBlockWidth () {
// Puts the browser into a read-write-read-write cycle.
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = `${box.offsetWidth}px`;
}
}
โค้ดนี้จะวนซ้ำกลุ่มย่อหน้าและตั้งค่าความกว้างของย่อหน้าแต่ละรายการให้ตรงกับความกว้างขององค์ประกอบชื่อ "box" แม้จะดูไม่อันตรายพอ แต่ปัญหาก็คือการวนซ้ำแต่ละครั้งจะอ่านค่ารูปแบบ (box.offsetWidth
) จากนั้นใช้ค่าดังกล่าวเพื่ออัปเดตความกว้างของย่อหน้า (paragraphs[i].style.width
) ทันทีเมื่อมีการวนซ้ำครั้งถัดไป เบราว์เซอร์จะต้องคำนึงถึงข้อเท็จจริงที่ว่ารูปแบบได้เปลี่ยนแปลงไปนับตั้งแต่ขอ offsetWidth
ครั้งล่าสุด (ในการทำซ้ำครั้งก่อน) จึงต้องใช้การเปลี่ยนแปลงรูปแบบ และเรียกใช้เลย์เอาต์ การดำเนินการนี้จะเกิดขึ้นในทุกรอบ
การแก้ไขสําหรับตัวอย่างนี้คือให้อ่านแล้วเขียนค่าอีกครั้ง
// Read.
const width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth () {
for (let i = 0; i < paragraphs.length; i++) {
// Now write.
paragraphs[i].style.width = `${width}px`;
}
}
หากต้องการรับประกันความปลอดภัย ให้ลองใช้ FastDOM ซึ่งจะจัดกลุ่มการอ่านและการเขียนให้คุณโดยอัตโนมัติ และควรป้องกันไม่ให้คุณเรียกใช้เลย์เอาต์แบบซิงค์แบบบังคับหรือเลย์เอาต์ที่ทำงานหนักโดยไม่ตั้งใจ