Quy trình thực tế để tối ưu INP khi tương tác bị chậm

6 phút đọc

Read in English →

Một trang có thể hiển thị rất nhanh mà vẫn cho cảm giác ì ạch ngay khi ai đó chạm vào menu hoặc chọn một biến thể sản phẩm. Interaction to Next Paint (INP) là chỉ số Core Web Vital phản ánh khoảng cách đó, và thường là chỉ số khó cải thiện nhất vì nguyên nhân nằm ở JavaScript của bạn chứ không phải ở server hay hình ảnh. Dưới đây là quy trình chúng tôi gợi ý để tìm ra các tương tác chậm và xử lý chúng theo thứ tự hợp lý.

Con số này thực sự có ý nghĩa gì

INP xem xét độ phản hồi của các tương tác trong toàn bộ một lượt truy cập và báo cáo một giá trị bằng hoặc gần với tương tác chậm nhất. Hướng dẫn của Google coi 200 ms trở xuống là tốt và trên 500 ms là kém, được đánh giá ở phân vị thứ 75 của các lượt tải trang thực tế, với mobile và desktop được báo cáo riêng. Chi tiết về phân vị này rất quan trọng: một cú click nhanh trên laptop của bạn nói rất ít về trải nghiệm của một khách truy cập thông thường trên điện thoại tầm trung.

Mỗi tương tác là tổng của ba giai đoạn:

  1. Input delay (độ trễ đầu vào): khoảng chờ trước khi event handler của bạn có thể bắt đầu, thường do main thread đang bận việc khác.
  2. Processing time (thời gian xử lý): các handler của bạn chạy mất bao lâu.
  3. Presentation delay (độ trễ hiển thị): thời gian trình duyệt cần để tính lại style, layout và vẽ khung hình tiếp theo.

Biết giai đoạn nào chiếm nhiều nhất sẽ cho bạn biết cần tìm ở đâu. Chờ lâu trước khi handler chạy thường trỏ đến các script khởi động hoặc mã của bên thứ ba; handler chạy chậm trỏ đến logic của chính bạn; phần đuôi kéo dài sau handler trỏ đến công việc xử lý DOM và layout.

Bắt đầu từ field data, sau đó tái hiện lỗi

Hãy bắt đầu với số liệu từ người dùng thật, lấy từ Chrome UX Report (hiển thị ở đầu PageSpeed Insights) hoặc từ hệ thống giám sát của riêng bạn. Sau đó tái hiện thao tác chậm trong bảng Performance của DevTools, tốt nhất là bật CPU throttling để chiếc máy mạnh của bạn không che mất vấn đề. Tìm các long task trên main thread và xem chúng nằm ở giai đoạn nào của tương tác.

Cũng nên biết rằng chỉ số này hiện đã đo được ngoài Chromium. web.dev thông báo vào tháng 12 năm 2025 rằng LCP và INP đã trở thành Baseline Newly available sau khi Safari 26.2 phát hành hỗ trợ, giúp xóa điểm mù đối với khách truy cập iOS trong số liệu phân tích của bạn. Bản thân Chrome UX Report vẫn chỉ lấy dữ liệu từ những người dùng Chrome đủ điều kiện, nên đừng kỳ vọng nó mô tả hành vi trên Safari.

Cách sửa 1: nhường main thread thay vì chiếm giữ

Long task, tức bất kỳ tác vụ nào dài hơn khoảng 50 ms, là thủ phạm phổ biến nhất. Khi một click handler làm mọi việc trong một lượt, trình duyệt không thể vẽ cho đến khi nó chạy xong. Hãy làm phần nhìn thấy được trước, rồi trả quyền điều khiển lại trước khi làm phần còn lại.

function yieldToMain() {
  if (globalThis.scheduler && scheduler.yield) {
    return scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function onAddToCart(event) {
  showSpinner(event.currentTarget);   // quick visual feedback
  await yieldToMain();                // let the browser paint
  await sendCartRequest();
  await yieldToMain();
  refreshCartCount();                 // less urgent work last
}

Bài tổng hợp các cách sửa Core Web Vitals của web.dev cho biết scheduler.yield() có sẵn trên các trình duyệt Chromium và Firefox bản mới nhưng chưa có trên Safari, vì vậy hãy giữ phần dự phòng setTimeout như ở trên và kiểm tra mức hỗ trợ hiện tại trước khi bỏ nó.

Cách sửa 2: ngừng ép trình duyệt tính lại layout

Presentation delay thường đến từ việc trộn lẫn thao tác ghi style với thao tác đọc layout. Đổi một class rồi đọc ngay offsetHeight khiến trình duyệt phải tính layout ngay lập tức, và làm vậy trong một vòng lặp sẽ nhân chi phí lên nhiều lần. Hãy đọc hết trước, ghi sau.

// Slow: read, write, read, write...
items.forEach((el) => {
  el.style.width = el.parentElement.offsetWidth + 'px';
});

// Better: batch the reads, then the writes
const widths = items.map((el) => el.parentElement.offsetWidth);
items.forEach((el, i) => {
  el.style.width = widths[i] + 'px';
});

DOM lớn làm mỗi bước trong số này đắt đỏ hơn. Làm phẳng markup lồng sâu, hoặc chỉ thêm nội dung khi cần, sẽ giảm khối lượng công việc cho mỗi tương tác. Với các phần dài nằm ngoài màn hình, thuộc tính CSS content-visibility cho phép trình duyệt bỏ qua việc render cho đến khi chúng gần vào khung nhìn.

Những chỗ theme cửa hàng thường gặp vấn đề

Hướng dẫn của chính Shopify về INP chỉ ra một số lỗi lặp đi lặp lại: bộ chọn biến thể cập nhật giá, hình ảnh và chữ trên nút cùng một lúc, cart drawer được tạo hiệu ứng qua width hoặc top, mega menu dựng một DOM lớn khi hover, stylesheet bị chia thành hàng trăm tệp, và các listener phân tích chạy mã đồng bộ ở mỗi lần click. Cách khắc phục theo cùng các ý tưởng ở trên: gom các cập nhật DOM, tạo hiệu ứng bằng transform: translateX() thay vì các thuộc tính layout, giữ markup menu trong trang và chỉ bật tắt hiển thị, và rà soát các app đã cài xem còn script nào vẫn được chèn vào sau khi đã gỡ.

Nếu storefront của bạn đã tích tụ nhiều vấn đề như vậy, dọn dẹp có cấu trúc sẽ nhanh hơn việc vá từng handler một. Dịch vụ phát triển và tối ưu của chúng tôi bao gồm đúng loại kiểm tra này, và các theme chúng tôi xây dựng giữ JavaScript ở mức nhỏ gọn một cách có chủ ý vì lý do tương tự.

Checklist ngắn

  • Tìm các tương tác tệ nhất trong field data trước khi động đến mã.
  • Xác định giai đoạn nào trong ba giai đoạn chiếm nhiều nhất trong profile đã throttle.
  • Cập nhật phần nhìn thấy được trước, rồi nhường main thread trước khi làm việc phụ.
  • Gom thao tác đọc và ghi DOM; tạo hiệu ứng bằng transform.
  • Gỡ hoặc hoãn các script chạy ở mỗi lần click.
  • Đo lại sau mỗi thay đổi thay vì chồng nhiều bản sửa một cách mù quáng.

Đọc thêm

← Tất cả bài viết