เลย์เอาต์คือจุดที่เบราว์เซอร์ค้นหาข้อมูลเรขาคณิตขององค์ประกอบต่างๆ เช่น ขนาดและตำแหน่งในหน้าเว็บ แต่ละองค์ประกอบจะมีข้อมูลการปรับขนาดอย่างชัดแจ้งหรือโดยนัยโดยอิงจาก CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบระดับบน กระบวนการนี้เรียกว่า "เลย์เอาต์" ใน Chrome
เลย์เอาต์คือจุดที่เบราว์เซอร์ค้นหาข้อมูลเรขาคณิตขององค์ประกอบต่างๆ นั่นคือ ขนาดและตำแหน่งในหน้าเว็บ แต่ละองค์ประกอบจะมีข้อมูลการปรับขนาดอย่างชัดแจ้งหรือโดยนัยโดยอิงจาก CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบระดับบน กระบวนการนี้เรียกว่า "เลย์เอาต์" ใน Chrome (และเบราว์เซอร์ที่ดึงมา เช่น Edge) และ Safari ใน Firefox เครื่องมือนี้เรียกว่า Reflow แต่กระบวนการยังคงมีประสิทธิภาพเหมือนเดิม
เช่นเดียวกับการคำนวณรูปแบบ ปัญหาเร่งด่วนเกี่ยวกับต้นทุนของเลย์เอาต์มีดังนี้
- จํานวนองค์ประกอบที่ต้องมีเลย์เอาต์ ซึ่งเป็นผลสืบเนื่องจากขนาด DOM ของหน้าเว็บ
- ความซับซ้อนของเลย์เอาต์เหล่านั้น
สรุป
- เลย์เอาต์มีผลโดยตรงต่อเวลาในการตอบสนองของการโต้ตอบ
- โดยปกติเลย์เอาต์จะกำหนดขอบเขตทั้งเอกสาร
- จำนวนองค์ประกอบ DOM จะมีผลต่อประสิทธิภาพการทำงาน คุณควรหลีกเลี่ยงการทริกเกอร์เลย์เอาต์เมื่อเป็นไปได้
- หลีกเลี่ยงการบังคับเลย์เอาต์แบบซิงโครนัสและการข้ามเลย์เอาต์ อ่านค่ารูปแบบแล้วจึงเปลี่ยนแปลงรูปแบบ
ผลกระทบของเลย์เอาต์ต่อเวลาในการตอบสนองของการโต้ตอบ
เมื่อผู้ใช้โต้ตอบกับหน้าเว็บ การโต้ตอบเหล่านั้นควรรวดเร็วที่สุดเท่าที่เป็นไปได้ ระยะเวลาที่ใช้ในการทำการโต้ตอบจนเสร็จ (สิ้นสุดเมื่อเบราว์เซอร์แสดงเฟรมถัดไปเพื่อแสดงผลลัพธ์ของการโต้ตอบ) เรียกว่าเวลาในการตอบสนองของการโต้ตอบ นี่คือแง่มุมหนึ่งของประสิทธิภาพของหน้าเว็บซึ่งเมตริกการโต้ตอบกับ 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.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 ซึ่งจะจัดกลุ่มการอ่านและเขียนให้คุณโดยอัตโนมัติ และจะช่วยป้องกันไม่ให้คุณเรียกใช้เลย์เอาต์แบบพร้อมกันที่บังคับหรือทับซ้อนเลย์เอาต์โดยไม่ตั้งใจ
รูปภาพหลักจาก Unsplash โดย Hal Gatewood