หลีกเลี่ยงการใช้เลย์เอาต์ที่มีขนาดใหญ่และซับซ้อน รวมถึงการกดเลย์เอาต์จำนวนมาก

เผยแพร่: 20 มีนาคม 2015 อัปเดตล่าสุด: 7 พฤษภาคม 2025

เลย์เอาต์คือส่วนที่เบราว์เซอร์จะคำนวณข้อมูลเชิงเรขาคณิตขององค์ประกอบ เช่น ขนาดและตําแหน่งในหน้า องค์ประกอบแต่ละรายการจะมีข้อมูลขนาดที่ชัดเจนหรือไม่ชัดเจนโดยอิงตาม CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบหลัก กระบวนการนี้เรียกว่าเลย์เอาต์ใน Chrome (และเบราว์เซอร์ที่มาจาก Chrome เช่น Edge) และ Safari ใน Firefox เรียกว่า "การจัดเรียงใหม่" แต่กระบวนการจะเหมือนกัน

เช่นเดียวกับการคำนวณสไตล์ ข้อกังวลทันทีเกี่ยวกับต้นทุนเลย์เอาต์มีดังนี้

  1. จํานวนองค์ประกอบที่ต้องจัดเลย์เอาต์ ซึ่งเป็นผลพลอยได้จากขนาด DOM ของหน้า
  2. ความซับซ้อนของเลย์เอาต์เหล่านั้น

สรุป

  • เลย์เอาต์มีผลโดยตรงต่อเวลาในการตอบสนองของการโต้ตอบ
  • โดยทั่วไปแล้ว เลย์เอาต์จะมีขอบเขตระดับทั้งเอกสาร
  • จำนวนองค์ประกอบ DOM จะส่งผลต่อประสิทธิภาพ คุณจึงควรหลีกเลี่ยงการทริกเกอร์เลย์เอาต์เมื่อเป็นไปได้
  • หลีกเลี่ยงเลย์เอาต์แบบซิงค์ที่บังคับและเลย์เอาต์ที่ทำงานหนักเกินไป ให้อ่านค่าสไตล์แล้วทําการเปลี่ยนแปลงสไตล์

ผลกระทบของเลย์เอาต์ต่อเวลาในการตอบสนองของการโต้ตอบ

เมื่อผู้ใช้โต้ตอบกับหน้าเว็บ การโต้ตอบเหล่านั้นควรรวดเร็วที่สุด ระยะเวลาที่ใช้ในการโต้ตอบจนเสร็จสมบูรณ์ ซึ่งสิ้นสุดเมื่อเบราว์เซอร์แสดงเฟรมถัดไปเพื่อแสดงผลลัพธ์ของการโต้ตอบ เรียกว่าเวลาในการตอบสนองของการโต้ตอบ นี่คือแง่มุมหนึ่งของประสิทธิภาพหน้าเว็บที่เมตริก Interaction to Next Paint จะวัด

ระยะเวลาที่เบราว์เซอร์ใช้ในการแสดงเฟรมถัดไปเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้เรียกว่าความล่าช้าในการแสดงผลของการโต้ตอบ เป้าหมายของการโต้ตอบคือการให้ฟีดแบ็กภาพเพื่อแจ้งให้ผู้ใช้ทราบว่ามีบางอย่างเกิดขึ้น และการอัปเดตภาพอาจเกี่ยวข้องกับงานเลย์เอาต์บางส่วนเพื่อให้บรรลุเป้าหมายดังกล่าว

คุณควรหลีกเลี่ยงเลย์เอาต์เมื่อเป็นไปได้เพื่อรักษา INP ของเว็บไซต์ให้ต่ำที่สุด หากหลีกเลี่ยงเลย์เอาต์ไม่ได้ คุณควรจำกัดการทำงานของเลย์เอาต์เพื่อให้เบราว์เซอร์แสดงเฟรมถัดไปได้อย่างรวดเร็ว

หลีกเลี่ยงเลย์เอาต์หากเป็นไปได้

เมื่อคุณเปลี่ยนสไตล์ เบราว์เซอร์จะตรวจสอบว่าการเปลี่ยนแปลงใดๆ จำเป็นต้องคำนวณเลย์เอาต์หรือไม่ และจำเป็นต้องอัปเดตต้นไม้การแสดงผลหรือไม่ การเปลี่ยนแปลง "คุณสมบัติเชิงเรขาคณิต" เช่น width, height, left หรือ top ทั้งหมดต้องใช้เลย์เอาต์

.box {
  width: 20px;
  height: 20px;
}

/**
  * Changing width and height
  * triggers layout.
  */

.box--expanded {
  width: 200px;
  height: 350px;
}

เกือบทุกครั้งเลยที่เลย์เอาต์จะมีขอบเขตระดับทั้งเอกสาร หากมีองค์ประกอบจำนวนมาก ระบบจะใช้เวลานานในการหาตําแหน่งและขนาดขององค์ประกอบทั้งหมด

หากหลีกเลี่ยงเลย์เอาต์ไม่ได้ สิ่งสำคัญคือให้ใช้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome อีกครั้งเพื่อดูว่าใช้เวลานานเท่าใด และพิจารณาว่าเลย์เอาต์เป็นสาเหตุของปัญหาคอขวดหรือไม่ ก่อนอื่น ให้เปิดเครื่องมือสำหรับนักพัฒนาเว็บ ไปที่แท็บไทม์ไลน์ แล้วกดบันทึกเพื่อโต้ตอบกับเว็บไซต์ เมื่อหยุดบันทึก คุณจะเห็นรายละเอียดประสิทธิภาพของเว็บไซต์ ดังนี้

DevTools ที่แสดงบล็อกเลย์เอาต์สีม่วงหลายรายการ
Chrome DevTools แสดงในเลย์เอาต์เป็นเวลานาน

เมื่อเจาะลึกการติดตามในตัวอย่างก่อนหน้า เราพบว่าแต่ละเฟรมใช้เวลากว่า 28 มิลลิวินาทีในเลย์เอาต์ ซึ่งสูงเกินไปเมื่อเรามีเวลา 16 มิลลิวินาทีในการแสดงเฟรมบนหน้าจอในภาพเคลื่อนไหว นอกจากนี้ คุณยังเห็นว่าเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะบอกขนาดของต้นไม้ (องค์ประกอบ 1,618 รายการในกรณีนี้) และจํานวนโหนดที่จําเป็นต้องจัดวาง (5 รายการในกรณีนี้)

โปรดทราบว่าคําแนะนําทั่วไปในที่นี้คือหลีกเลี่ยงเลย์เอาต์ทุกครั้งที่เป็นไปได้ แต่บางครั้งก็หลีกเลี่ยงไม่ได้ ในกรณีที่หลีกเลี่ยงเลย์เอาต์ไม่ได้ โปรดทราบว่าต้นทุนของเลย์เอาต์มีความเกี่ยวข้องกับขนาดของ DOM แม้ว่าความสัมพันธ์ระหว่าง 2 รายการนี้จะไม่เชื่อมโยงกันมากนัก แต่โดยทั่วไป DOM ที่ใหญ่ขึ้นจะมีต้นทุนเลย์เอาต์สูงกว่า

หลีกเลี่ยงเลย์เอาต์แบบซิงค์ที่บังคับ

การส่งเฟรมไปยังหน้าจอมีลําดับดังนี้

การใช้ Flexbox เป็นเลย์เอาต์
ขั้นตอนการแสดงผล

โดยระบบจะเรียกใช้ 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`;
  }
}

ระบุเลย์เอาต์แบบซิงค์แบบบังคับและการทํางานหนัก

เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์มีข้อมูลเชิงลึกเกี่ยวกับการจัดเรียงใหม่แบบบังคับเพื่อช่วยให้คุณระบุกรณีของเลย์เอาต์แบบซิงค์แบบบังคับ (หรือที่เรียกว่า "การจัดเรียงใหม่แบบบังคับ") ได้อย่างรวดเร็ว

เครื่องมือสำหรับนักพัฒนาเว็บแสดงข้อมูลเชิงลึกเกี่ยวกับการบังคับให้จัดเรียงใหม่ซึ่งระบุฟังก์ชันที่ชื่อ &quot;w&quot; ซึ่งทําให้เกิดการบังคับให้จัดเรียงใหม่
ข้อมูลเชิงลึกเกี่ยวกับการบังคับให้จัดเรียงใหม่ของ Chrome DevTools

นอกจากนี้ คุณยังระบุเลย์เอาต์แบบซิงค์แบบบังคับในช่องได้โดยใช้การระบุแหล่งที่มาของสคริปต์ Long Animation Frame API โดยใช้พร็อพเพอร์ตี้ forcedStyleAndLayoutDuration