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

เลย์เอาต์คือจุดที่เบราว์เซอร์ค้นหาข้อมูลเรขาคณิตขององค์ประกอบต่างๆ เช่น ขนาดและตำแหน่งในหน้าเว็บ แต่ละองค์ประกอบจะมีข้อมูลการปรับขนาดอย่างชัดแจ้งหรือโดยนัยโดยอิงจาก CSS ที่ใช้ เนื้อหาขององค์ประกอบ หรือองค์ประกอบระดับบน กระบวนการนี้เรียกว่า "เลย์เอาต์" ใน Chrome

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

เช่นเดียวกับการคำนวณรูปแบบ ปัญหาเร่งด่วนเกี่ยวกับต้นทุนของเลย์เอาต์มีดังนี้

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

สรุป

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

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

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

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

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

หลีกเลี่ยงการจัดวางเมื่อเป็นไปได้

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

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

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

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

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

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

เครื่องมือสำหรับนักพัฒนาเว็บแสดงเป็นเวลานานใน Layout

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

โปรดทราบว่าคำแนะนำทั่วไปในที่นี้คือให้หลีกเลี่ยงการจัดวางเมื่อเป็นไปได้ แต่ไม่เสมอไปที่จะหลีกเลี่ยงการจัดวาง ในกรณีที่คุณหลีกเลี่ยงการออกแบบไม่ได้ ให้สังเกตว่าค่าใช้จ่ายของการออกแบบมีความสัมพันธ์กับขนาดของ DOM แม้ว่าความสัมพันธ์ระหว่างทั้งสองจะไม่ได้เชื่อมโยงกันอย่างเหนียวแน่น แต่โดยทั่วไปแล้ว 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.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