Cumulative Layout Shift (CLS - Thay đổi Bố cục Tích lũy) thường được mô tả là một “vấn đề về bố cục,” nhưng trong thực tế, nó thường xuyên là một vấn đề về font chữ. Bài viết này ghi lại cách một hệ thống dù đã được thiết lập đúng chuẩn vẫn tạo ra điểm CLS gần 1.0 và cách việc sửa đổi font metrics cũng như độ ổn định bố cục đã giúp giảm CLS xuống gần bằng không mà không cần thay đổi thiết kế hay nội dung.
Trong kỷ nguyên của Core Web Vitals, CLS đã trở thành một chỉ số quan trọng cho cả SEO và trải nghiệm người dùng. Điểm CLS cao cho thấy một trang web "nhảy múa" bất thường, nơi các phần tử di chuyển không mong muốn, dẫn đến việc người dùng bấm nhầm và gây ức chế. Trong khi hình ảnh thiếu kích thước thường bị đổ lỗi, thì typography (nghệ thuật chữ) mới là "thủ phạm thầm lặng" đằng sau những đợt dịch chuyển bố cục lớn.
Vấn đề: CLS Gần 1.0 trên Thiết bị Di động
Lighthouse báo cáo điểm CLS xấp xỉ 0.93 trên di động—một con số đưa trang web vào nhóm "Kém" (Poor). Báo cáo chỉ ra rõ ràng hai yếu tố chính gây ra điều này:
- Các Web font được tải từ
fonts.gstatic.com - Các dịch chuyển bố cục nhỏ bên trong danh sách blog và các thẻ bài viết (cards)
Điều làm cho trường hợp này gây bối rối là các phương pháp tối ưu tốt nhất dường như đã được áp dụng. Font chữ đã sử dụng font-display: swap, hình ảnh có kích thước hợp lý và không có việc chèn DOM lạ trong quá trình tải. Mặc dù vậy, bố cục vẫn bị dịch chuyển mạnh ngay sau khi font chữ hoàn tất việc tải xuống.
Tại sao font-display: swap là chưa đủ?
Sự hiểu lầm phổ biến trong nhiều trường hợp CLS là giả định rằng chỉ cần font-display: swap là đủ để ngăn chặn dịch chuyển bố cục. Thực tế, nó chỉ đảm bảo văn bản hiển thị sớm bằng cách sử dụng font dự phòng của hệ thống. Nó không đảm bảo rằng font dự phòng và web font cuối cùng có cùng kích thước hoặc "metrics" (các thông số đo lường font).
Trong trường hợp này, font dự phòng (thường là Arial hoặc sans-serif mặc định) có các giá trị ascent (phần nhô lên), descent (phần hạ xuống) và line gap (khoảng cách dòng) khác với web font cuối cùng (Inter). Khi font thật được tải xong, mỗi dòng văn bản thay đổi chiều cao một chút. Những thay đổi nhỏ này tích tụ lại trên một bài viết dài hoặc một danh sách các thẻ, đẩy toàn bộ bố cục xuống dưới. Vì điều này xảy ra sau lần hiển thị đầu tiên, Lighthouse đã ghi nhận đó là một đợt dịch chuyển tích lũy lớn.
Chẩn đoán Nguyên nhân Gốc rễ
Để xác định chính xác nguyên nhân, chúng tôi đã sử dụng tính năng "Layout Shift Regions" trong Chrome DevTools. Khi bật tính năng này, các vùng hình chữ nhật màu xanh sẽ nhấp nháy mỗi khi có sự dịch chuyển. Chẩn đoán xác nhận rằng vấn đề không phải do JavaScript hay chèn nội dung muộn, mà là sự sai lệch font metrics.
Trình duyệt vẽ font dự phòng trước. Sau đó, khi tệp .woff2 tải xong, nó thay thế các ký tự. Nếu font mới cao hơn 5%, mọi phần tử tiếp theo trên trang sẽ bị đẩy xuống 5%, tạo ra tác động CLS khổng lồ.
Giải pháp Chính: Ghi đè Font Metrics (Font Metrics Overrides)
Giải pháp hiệu quả nhất là thêm các thuộc tính ghi đè metrics trực tiếp vào khai báo @font-face. Tính năng CSS tương đối mới này cho phép lập trình viên yêu cầu trình duyệt điều chỉnh kích thước của font dự phòng sao cho khớp với web font ngay cả trước khi nó tải xong.
Ví dụ đơn giản về giải pháp:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
/* Điều chỉnh metrics của font dự phòng để khớp với Inter */
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 100%;
}
Với các thuộc tính này, trình duyệt sẽ "kéo dãn" hoặc "nén" font dự phòng để chiếm đúng khoảng trống chiều dọc như font cuối cùng. Khi quá trình hoán đổi (swap) diễn ra, văn bản thay thế các ký tự dự phòng một cách hoàn hảo và bố cục vẫn giữ vững ổn định.
Chọn Font Dự phòng Phù hợp
Một cải tiến quan trọng khác là xác định rõ ràng các font dự phòng thay vì dựa vào mặc định của trình duyệt. Bằng cách chọn một font hệ thống có nét tương đồng với web font mục tiêu, các thuộc tính ghi đè sẽ làm việc hiệu quả hơn.
body {
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}
Chuỗi font này đảm bảo typography dễ dự đoán hơn trong lần hiển thị đầu tiên và giảm thiểu khả năng sai lệch metrics trên các hệ điều hành khác nhau như iOS, Android và Windows.
Ổn định Bố cục Danh sách Blog
Mặc dù font chữ là nguyên nhân chính, bố cục danh sách blog cũng đóng góp những dịch chuyển nhỏ. Chiều cao của mỗi thẻ bài viết phụ thuộc vào việc hiển thị văn bản, vì vậy ngay cả những thay đổi nhỏ về font cũng ảnh hưởng đến toàn bộ container. Chúng tôi đã kết hợp sửa font với tính ổn định cấu trúc CSS:
.blog-card {
min-height: 160px; /* Giữ chỗ không gian tối thiểu */
}
.blog-card img {
width: 100%;
aspect-ratio: 16 / 9; /* Ngăn nhảy bố cục khi ảnh tải xong */
height: auto;
}
Kết quả
Sau khi áp dụng các giải pháp này, kết quả đạt được ngay lập tức và có thể đo lường:
- CLS giảm từ ~0.93 xuống còn 0.002 (gần như bằng không).
- Lighthouse không còn gắn cờ các tệp font là nguồn gây dịch chuyển bố cục.
- Hiện tượng "Flash of Unstyled Text" (FOUT) hầu như không thể nhận thấy vì bố cục không bị dịch chuyển.
- Hiệu suất SEO cải thiện khi trang web vượt qua tất cả các yêu cầu của Core Web Vitals.
Góc nhìn Chiến lược
Các vấn đề về CLS thường bị chẩn đoán nhầm là các vấn đề JavaScript phức tạp trong khi nguyên nhân thực sự nằm ở typography cơ bản. Đối với các bên liên quan về mặt kỹ thuật, bài học rút ra rất rõ ràng: hiệu năng không chỉ là việc một tệp tải nhanh như thế nào, mà là cách trình duyệt xử lý tệp đó trong chu kỳ hiển thị (rendering lifecycle). Bằng cách tận dụng các thuộc tính CSS hiện đại như ascent-override, bạn có thể đạt được điểm hiệu năng tối đa mà không cần hy sinh bản sắc hình ảnh của thương hiệu.