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 của 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 rõ ràng hoặc ngầm định về kích thước 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 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 chúng trong trang. Mỗi phần tử sẽ có thông tin rõ ràng hoặc ngầm định về kích thước 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 (và các trình duyệt phát sinh như Edge) và Safari. Trong Firefox, quy trình này được gọi là Reflow, nhưng quá trình này thực sự giống nhau.

Tương tự như việc tính toán kiểu, mối quan tâm trước mắt về chi phí bố cục là:

  1. Số lượng phần tử yêu cầu bố cục, đây là sản phẩm phụ của kích thước DOM của trang.
  2. Sự phức tạp của các 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ó phạm vi 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 cục bộ; đọc các giá trị kiểu rồi thay đổi kiểu.

Tác độ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 hoạt động tương tác đó phải diễn ra nhanh nhất có thể. Khoảng thời gian cần thiết để một lượt tương tác hoàn tất – kết thúc khi trình duyệt hiển thị khung hình tiếp theo để hiển thị 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 với Nội dung hiển thị tiếp theo đo lường.

Khoảng thời gian để trình duyệt hiển thị khung tiếp theo để phản hồi một lượt tương tác của người dùng được gọi là độ trễ hiển thị của lượt tương tác đó. Mục tiêu của 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 sự kiện nào đó đã xảy ra và việc cập nhật bằng hình ảnh có thể cần đến một số lượng công việc bố cục để đạt được mục tiêu đó.

Để duy trì INP của trang web ở mức thấp nhất có thể, bạn nê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, bạn cần phải giới hạn công việc của bố cục đó để trình duyệt có thể nhanh chóng hiển thị khung tiếp theo.

Tránh sử dụng 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 không và cập nhật cây hiển thị đó. Các 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 thuộc 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 để tìm ra 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 mất bao lâu 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 bản ghi và tương tác với trang web của bạn. Khi ngừng ghi, bạn sẽ thấy bảng phân tích về hiệu suất của trang web:

Công cụ cho nhà phát triển cho thấy một khoảng thời gian dài trong 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 bên trong bố cục cho mỗi khung hình, trong khi chúng ta có 16 mili giây để có được một khung hình trên màn hình trong ảnh động thì quá cao. Bạn cũng có thể thấy rằng Công cụ cho nhà phát triển 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ố 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 nhớ rằng chi phí bố cục có mối liên hệ với kích thước của DOM. Mặc dù mối quan hệ giữa hai tài khoản không được liên kết chặt chẽ, nhưng những 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

Vận chuyển một khung hình đến màn hình có thứ tự sau:

Dùng flexbox làm bố cục.

Trước tiên, JavaScript chạy, rồi tính toán kiểu, sau đó là bố cục rồi đến. 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 sẽ được biết và có sẵn để bạn truy vấn. Ví dụ: nếu bạn muốn ghi chiều cao của một phần tử (hãy 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ẽ trở nên khó khăn nếu bạn đã thay đổi kiểu của hộp trước khi yêu cầu chiều cao của hộp:

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 kiểu (vì thêm lớp super-big) rồi sau đó chạy bố cục. Chỉ khi đó, máy chủ mới có thể trả về chiều cao chính xác. Đây là tác vụ không cần thiết và có thể tốn kém.

Do đó, bạn phải luôn đọc theo lô và thực hiện chúng trước (khi trình duyệt có thể sử dụng giá trị bố cục của khung trước đó) rồi thực hiện mọi thao tác ghi:

Thực hiện đúng cách hàm trên sẽ là:

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 phải á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 và bố cục một cách đồng bộ và sớm hơn trình duyệt mong muốn có thể là nút thắt cổ chai tiềm ẩn và bạn cũng không nên làm như vậy.

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

Có một cách để làm cho bố cục đồng bộ bắt buộc thậm chí còn tệ hơn: thực hiện nhiều bố cục liên tiếp nhanh chóng. Hãy xem mã này:

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 đoạn và đặt chiều rộng của mỗi đoạn cho phù hợp với chiều rộng của một phần tử được gọi là "box". Trông có vẻ vô hại, nhưng vấn đề là mỗi lần lặp 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ị này để cập nhật chiều rộng của đoạn văn (paragraphs[i].style.width). Trong lần lặp tiếp theo của vòng lặp, trình duyệt phải xét đến thực tế là kiểu đã thay đổi kể từ lần gần nhất offsetWidth được yêu cầu (trong vòng lặp trước) nên phải áp dụng các thay đổi về kiểu rồi 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 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, một tính năng tự động đọc và ghi cho bạn, đồng thời giúp bạn không vô tình kích hoạt các bố cục đồng bộ hoặc bố cục bị đơ.

Hình ảnh chính từ Unsplash của Hal Gatewood.