Tránh bố cục lớn, phức tạp và tình trạng bập bênh bố cục

Bố cục là nơi trình duyệt tìm ra thông tin hình học cho các phần tử – kích thước và vị trí của các phần tử đó trong trang. Mỗi phần tử sẽ có thông tin kích thước rõ ràng hoặc ngầm ẩn dựa trên CSS đã được sử dụng, nội dung của phần tử hoặc phần tử mẹ. Quá trình này được gọi là Bố cục trong Chrome.

Bố cục là nơi trình duyệt tìm ra thông tin hình học cho các phần tử: kích thước và vị trí của các phần tử đó trong trang. Mỗi phần tử sẽ có thông tin định cỡ rõ ràng hoặc ngầm ẩn dựa trên CSS đã sử dụng, nội dung của phần tử hoặc phần tử mẹ. Quy trình này được gọi là Bố cục trong Chrome (và các trình duyệt phái sinh như Edge) và Safari. Trong Firefox, tính năng này được gọi là Reflow (Lưu lại luồng), nhưng quy trình này vẫn giống như vậy.

Tương tự như việc tính toán kiểu, những vấn đề cấp thiết về chi phí bố cục là:

  1. Số lượng phần tử cần có bố cục, là sản phẩm phụ của kích thước DOM của trang.
  2. Độ phức tạp của những bố cục đó.

Tóm tắt

  • Bố cục có ảnh hưởng trực tiếp đến độ trễ tương tác
  • Bố cục thường được áp dụng cho toàn bộ tài liệu.
  • Số lượng phần tử DOM sẽ ảnh hưởng đến hiệu suất, bạn nên tránh kích hoạt bố cục bất cứ khi nào có thể.
  • Tránh bố cục đồng bộ bắt buộc và bố cục bị lỗi; đọc giá trị kiểu rồi thay đổi kiểu.

Ảnh hưởng của bố cục đối với độ trễ tương tác

Khi người dùng tương tác với trang, các lượt tương tác đó phải diễn ra nhanh nhất có thể. Khoảng thời gian cần thiết để hoàn tất một lượt tương tác (kết thúc khi trình duyệt hiển thị khung hình tiếp theo để cho thấy kết quả của lượt tương tác) được gọi là độ trễ tương tác. Đây là một khía cạnh của hiệu suất trang mà chỉ số Lượt tương tác đến nội dung hiển thị tiếp theo đo lường.

Khoảng thời gian mà trình duyệt cần để hiển thị khung hình tiếp theo để phản hồi tương tác của người dùng được gọi là độ trễ hiển thị của tương tác. Mục tiêu của một lượt tương tác là cung cấp phản hồi trực quan để báo hiệu cho người dùng rằng đã có điều gì đó xảy ra. Các bản cập nhật trực quan có thể liên quan đến một số công việc bố cục để đạt được mục tiêu đó.

Để giữ INP của trang web thấp nhất có thể, bạn cần tránh sử dụng bố cục khi có thể. Nếu không thể tránh hoàn toàn bố cục, điều quan trọng là bạn phải giới hạn công việc bố cục đó để trình duyệt có thể hiển thị khung hình tiếp theo một cách nhanh chóng.

Tránh bố cục bất cứ khi nào có thể

Khi bạn thay đổi kiểu, trình duyệt sẽ kiểm tra xem có thay đổi nào yêu cầu tính toán bố cục và cập nhật cây kết xuất đó hay không. Mọi thay đổi đối với "thuộc tính hình học", chẳng hạn như chiều rộng, chiều cao, bên trái hoặc trên cùng đều yêu cầu bố cục.

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

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

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

Bố cục hầu như luôn nằm trong phạm vi toàn bộ tài liệu. Nếu bạn có nhiều phần tử, sẽ mất nhiều thời gian để xác định vị trí và kích thước của tất cả các phần tử đó.

Nếu không thể tránh bố cục thì điều quan trọng là một lần nữa sử dụng Công cụ của Chrome cho nhà phát triển để xem thời gian cần thiết và xác định xem bố cục có phải là nguyên nhân gây ra nút thắt cổ chai hay không. Trước tiên, hãy mở Công cụ cho nhà phát triển, chuyển đến thẻ Dòng thời gian, nhấn vào ghi lại và tương tác với trang web của bạn. Khi dừng ghi, bạn sẽ thấy bảng chi tiết về hiệu suất của trang web:

Công cụ cho nhà phát triển hiển thị thời gian dài trong Layout (Bố cục).

Khi tìm hiểu dấu vết trong ví dụ trên, chúng ta thấy rằng hơn 28 mili giây được dành cho bố cục cho mỗi khung hình, khi chúng ta có 16 mili giây để lấy một khung hình trên màn hình trong ảnh động, thì thời gian này là quá cao. Bạn cũng có thể thấy rằng DevTools sẽ cho bạn biết kích thước cây (1.618 phần tử trong trường hợp này) và số lượng nút cần bố cục (5 trong trường hợp này).

Xin lưu ý rằng lời khuyên chung ở đây là tránh bố cục bất cứ khi nào có thể – nhưng không phải lúc nào bạn cũng có thể tránh bố cục. Trong trường hợp bạn không thể tránh bố cục, hãy lưu ý rằng chi phí bố cục có mối quan hệ với kích thước của DOM. Mặc dù mối quan hệ giữa hai lớp này không được kết hợp chặt chẽ, nhưng các DOM lớn hơn thường sẽ phải chịu chi phí bố cục cao hơn.

Tránh bố cục đồng bộ bắt buộc

Việc vận chuyển một khung hình tới màn hình có thứ tự sau:

Sử dụng flexbox làm bố cục.

Trước tiên, JavaScript chạy, sau đó tính toán kiểu, sau đó bố cục. Tuy nhiên, bạn có thể buộc trình duyệt thực hiện bố cục sớm hơn bằng JavaScript. Đây được gọi là bố cục đồng bộ bắt buộc.

Điều đầu tiên cần lưu ý là khi JavaScript chạy, tất cả giá trị bố cục cũ từ khung trước được xác định và có sẵn để bạn truy vấn. Ví dụ: nếu muốn viết chiều cao của một phần tử (gọi là "hộp") ở đầu khung, bạn có thể viết một số mã như sau:

// 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);
}

Mọi thứ sẽ có vấn đề nếu bạn đã thay đổi kiểu của hộp trước khi bạn yêu cầu về chiều cao:

function logBoxHeight () {
  box.classList.add('super-big');

  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

Bây giờ, để trả lời câu hỏi về chiều cao, trước tiên, trình duyệt phải áp dụng thay đổi về kiểu (do thêm lớp super-big) và sau đó chạy bố cục. Chỉ khi đó, hàm này mới có thể trả về chiều cao chính xác. Đây là công việc không cần thiết và có thể tốn kém.

Do đó, bạn phải luôn thực hiện các lượt đọc kiểu theo lô và thực hiện các lượt đọc đó trước tiên (trong đó trình duyệt có thể sử dụng các giá trị bố cục của khung trước đó), sau đó thực hiện mọi lượt ghi:

Khi hoàn tất, hàm trên sẽ có dạng như sau:

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

Trong hầu hết trường hợp, bạn không cần áp dụng kiểu rồi truy vấn giá trị; việc sử dụng giá trị của khung cuối cùng là đủ. Việc chạy các phép tính kiểu và bố cục đồng bộ và sớm hơn so với mong muốn của trình duyệt có thể là nút thắt cổ chai tiềm ẩn và không phải là điều bạn thường muốn làm.

Tránh tình trạng bố cục bị đơ

Có một cách để làm cho bố cục đồng bộ bắt buộc trở nên tệ hơn nữa: thực hiện nhiều bố cục như vậy liên tiếp. Hãy xem mã sau:

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`;
  }
}

Mã này lặp lại trên một nhóm các đoạn văn và đặt chiều rộng của mỗi đoạn văn để khớp với chiều rộng của một phần tử có tên là "hộp". Mã này có vẻ không gây hại, nhưng vấn đề là mỗi lần lặp lại của vòng lặp sẽ đọc một giá trị kiểu (box.offsetWidth) rồi ngay lập tức sử dụng giá trị đó để cập nhật chiều rộng của một đoạn văn bản (paragraphs[i].style.width). Ở lần lặp lại tiếp theo của vòng lặp, trình duyệt phải tính đến việc các kiểu đã thay đổi kể từ lần yêu cầu offsetWidth gần đây nhất (trong lần lặp lại trước đó), do đó, trình duyệt phải áp dụng các thay đổi về kiểu và chạy bố cục. Điều này sẽ xảy ra trong mỗi lần lặp lại!

Cách khắc phục cho mẫu này là một lần nữa đọc rồi ghi các giá trị:

// Read.
const width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth () {
  for (let i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = `${width}px`;
  }
}

Nếu bạn muốn đảm bảo an toàn, hãy cân nhắc sử dụng FastDOM. Tính năng này tự động phân lô các lượt đọc và ghi cho bạn, đồng thời ngăn bạn vô tình kích hoạt bố cục đồng bộ bắt buộc hoặc tình trạng đơ bố cục.