Tối ưu hoá độ trễ khi nhập

Tìm hiểu về độ trễ đầu vào và tìm hiểu các kỹ thuật để giảm độ trễ này nhằm tăng tính tương tác.

Các hoạt động tương tác trên web là những hoạt động phức tạp, với đủ loại hoạt động diễn ra trong trình duyệt để thúc đẩy các hoạt động đó. Tuy nhiên, điểm chung của tất cả những thành phần này là chúng đều gây ra một số độ trễ đầu vào trước khi các lệnh gọi lại sự kiện bắt đầu chạy. Trong hướng dẫn này, bạn sẽ tìm hiểu về độ trễ đầu vào và những việc bạn có thể làm để giảm thiểu độ trễ này, nhờ đó các hoạt động tương tác trên trang web của bạn sẽ diễn ra nhanh hơn.

Độ trễ khi nhập thông tin là gì?

Độ trễ đầu vào là khoảng thời gian bắt đầu từ khi người dùng tương tác lần đầu với một trang (chẳng hạn như nhấn vào màn hình, nhấp bằng chuột hoặc nhấn một phím) cho đến khi lệnh gọi lại sự kiện cho hoạt động tương tác bắt đầu chạy. Mỗi lượt tương tác đều bắt đầu bằng một khoảng thời gian trễ đầu vào.

Hình ảnh trực quan đơn giản về độ trễ khi nhập. Ở bên trái, có hình vẽ đường nét của con trỏ chuột với một tia sáng phía sau, biểu thị sự bắt đầu của một lượt tương tác. Ở bên phải là hình vẽ đường nét của một bánh răng, biểu thị thời điểm trình xử lý sự kiện cho một lượt tương tác bắt đầu chạy. Khoảng trống ở giữa được ghi nhận là độ trễ đầu vào bằng dấu ngoặc nhọn.
Cơ chế đằng sau độ trễ đầu vào. Khi hệ điều hành nhận được một dữ liệu đầu vào, dữ liệu đó phải được truyền đến trình duyệt trước khi bắt đầu tương tác. Quá trình này mất một khoảng thời gian nhất định và có thể tăng lên do công việc hiện có trên luồng chính.

Không thể tránh khỏi một phần độ trễ đầu vào: hệ điều hành luôn mất một khoảng thời gian nhất định để nhận dạng sự kiện đầu vào và chuyển sự kiện đó đến trình duyệt. Tuy nhiên, phần độ trễ đầu vào đó thường không đáng kể, và có những việc khác xảy ra trên chính trang có thể khiến độ trễ đầu vào đủ lâu để gây ra vấn đề.

Cách suy nghĩ về độ trễ đầu vào

Nói chung, bạn nên rút ngắn mọi phần của một lượt tương tác càng nhiều càng tốt để trang web của bạn có nhiều khả năng đáp ứng ngưỡng "tốt" của chỉ số Lượt tương tác đến nội dung hiển thị tiếp theo (INP), bất kể người dùng sử dụng thiết bị nào. Việc kiểm soát độ trễ đầu vào chỉ là một phần trong việc đáp ứng ngưỡng đó.

Do đó, bạn nên cố gắng đạt được độ trễ đầu vào ngắn nhất có thể để đáp ứng ngưỡng "tốt" của INP. Tuy nhiên, bạn cần lưu ý rằng bạn không thể loại bỏ hoàn toàn độ trễ đầu vào. Miễn là bạn tránh được quá nhiều hoạt động trên luồng chính trong khi người dùng đang cố gắng tương tác với trang của bạn, độ trễ đầu vào sẽ đủ thấp để tránh các vấn đề.

Cách giảm thiểu độ trễ đầu vào

Như đã nói trước đó, bạn không thể tránh khỏi một số độ trễ đầu vào, nhưng mặt khác, bạn có thể tránh được một số độ trễ đầu vào. Dưới đây là một số điều cần cân nhắc nếu bạn gặp phải tình trạng trễ đầu vào kéo dài.

Tránh các bộ hẹn giờ định kỳ khởi động quá nhiều thao tác trên luồng chính

Có 2 hàm hẹn giờ thường dùng trong JavaScript có thể góp phần gây ra độ trễ đầu vào: setTimeoutsetInterval. Điểm khác biệt giữa hai phương thức này là setTimeout lên lịch một lệnh gọi lại để chạy sau một khoảng thời gian cụ thể. Mặt khác, setInterval lên lịch cho một lệnh gọi lại để chạy mỗi n mili giây vĩnh viễn hoặc cho đến khi bộ hẹn giờ dừng bằng clearInterval.

setTimeout không phải là vấn đề tự thân. Trên thực tế, nó có thể hữu ích trong việc tránh các tác vụ dài. Tuy nhiên, điều này còn tuỳ thuộc vào thời điểm xảy ra thời gian chờ và liệu người dùng có cố gắng tương tác với trang khi lệnh gọi lại thời gian chờ chạy hay không.

Ngoài ra, setTimeout có thể chạy trong một vòng lặp hoặc đệ quy, trong đó nó hoạt động giống như setInterval, mặc dù tốt nhất là không lên lịch cho lần lặp lại tiếp theo cho đến khi lần lặp lại trước đó hoàn tất. Mặc dù điều này có nghĩa là vòng lặp sẽ nhường chỗ cho luồng chính mỗi khi setTimeout được gọi, nhưng bạn nên cẩn thận để đảm bảo lệnh gọi lại của vòng lặp không thực hiện quá nhiều thao tác.

setInterval chạy một lệnh gọi lại theo một khoảng thời gian nhất định, do đó, nhiều khả năng sẽ cản trở các hoạt động tương tác. Điều này là do không giống như một lệnh gọi setTimeout duy nhất (là một lệnh gọi lại một lần mà có thể cản trở hoạt động tương tác của người dùng), bản chất định kỳ của setInterval khiến lệnh gọi này sẽ cản trở hoạt động tương tác, do đó làm tăng độ trễ đầu vào của hoạt động tương tác.

Ảnh chụp màn hình của trình phân tích hiệu suất trong Công cụ của Chrome cho nhà phát triển minh hoạ độ trễ đầu vào. Một tác vụ do hàm hẹn giờ kích hoạt sẽ xảy ra ngay trước khi người dùng bắt đầu một lượt tương tác nhấp. Tuy nhiên, bộ tính giờ sẽ kéo dài độ trễ đầu vào, khiến các lệnh gọi lại sự kiện của hoạt động tương tác chạy muộn hơn so với bình thường.
Bộ hẹn giờ do lệnh gọi setInterval trước đó đăng ký, góp phần gây ra độ trễ đầu vào như mô tả trong bảng điều khiển hiệu suất của Chrome DevTools. Độ trễ đầu vào được thêm vào khiến các lệnh gọi lại sự kiện cho hoạt động tương tác chạy muộn hơn so với bình thường.

Nếu bộ hẹn giờ xuất hiện trong mã của bên thứ nhất, thì bạn có thể kiểm soát chúng. Đánh giá xem bạn có cần chúng hay không, hoặc cố gắng giảm thiểu công việc trong chúng càng nhiều càng tốt. Tuy nhiên, bộ hẹn giờ trong tập lệnh của bên thứ ba lại là một chuyện khác. Bạn thường không kiểm soát được những gì mà một tập lệnh bên thứ ba thực hiện, đồng thời việc khắc phục các vấn đề về hiệu suất trong mã bên thứ ba thường liên quan đến việc làm việc với các bên liên quan để xác định xem một tập lệnh bên thứ ba cụ thể có cần thiết hay không. Nếu cần thiết, hãy liên hệ với nhà cung cấp tập lệnh bên thứ ba để xác định những việc có thể làm để khắc phục các vấn đề về hiệu suất mà họ có thể gây ra trên trang web của bạn.

Tránh các tác vụ dài

Một cách để giảm thiểu độ trễ đầu vào dài là tránh các tác vụ dài. Khi bạn có quá nhiều việc cần làm trên luồng chính khiến luồng chính bị chặn trong quá trình tương tác, điều đó sẽ làm tăng độ trễ đầu vào cho người dùng trước khi các tác vụ dài có cơ hội hoàn thành.

Hình ảnh minh hoạ về thời gian các tác vụ kéo dài độ trễ đầu vào. Ở trên cùng, một lượt tương tác xảy ra ngay sau khi một tác vụ đơn lẻ chạy trong thời gian dài, gây ra độ trễ đầu vào đáng kể khiến các lệnh gọi lại sự kiện chạy muộn hơn nhiều so với thời gian cần thiết. Ở dưới cùng, một lượt tương tác xảy ra gần như cùng lúc, nhưng tác vụ dài được chia thành nhiều tác vụ nhỏ hơn bằng cách tạo ra, cho phép các lệnh gọi lại sự kiện của lượt tương tác chạy sớm hơn nhiều.
Hình ảnh minh hoạ những gì xảy ra với các hoạt động tương tác khi các tác vụ quá dài và trình duyệt không thể phản hồi đủ nhanh với các hoạt động tương tác, so với khi các tác vụ dài hơn được chia thành các tác vụ nhỏ hơn.

Ngoài việc giảm thiểu lượng công việc bạn thực hiện trong một tác vụ (bạn luôn phải cố gắng thực hiện ít công việc nhất có thể trên luồng chính), bạn có thể cải thiện khả năng phản hồi đối với hoạt động đầu vào của người dùng bằng cách chia nhỏ các tác vụ dài.

Lưu ý đến trường hợp tương tác trùng lặp

Một phần đặc biệt khó khăn trong việc tối ưu hoá INP có thể là khi bạn có các lượt tương tác trùng lặp. Tương tác chồng chéo có nghĩa là sau khi tương tác với một phần tử, bạn thực hiện một tương tác khác với trang trước khi tương tác ban đầu có cơ hội hiển thị khung hình tiếp theo.

Hình ảnh minh hoạ thời điểm các tác vụ có thể trùng lặp để tạo ra độ trễ đầu vào dài. Trong hình minh hoạ này, một lượt tương tác nhấp chuột trùng với một lượt tương tác keydown để tăng độ trễ đầu vào cho lượt tương tác keydown.
Hình ảnh minh hoạ 2 lượt tương tác đồng thời trong trình phân tích hiệu suất trong Công cụ cho nhà phát triển của Chrome. Hoạt động kết xuất trong lượt tương tác nhấp chuột ban đầu gây ra độ trễ đầu vào cho lượt tương tác bàn phím tiếp theo.

Các nguồn tương tác trùng lặp có thể đơn giản như việc người dùng thực hiện nhiều lượt tương tác trong một khoảng thời gian ngắn. Điều này có thể xảy ra khi người dùng nhập vào các trường biểu mẫu, nơi có thể xảy ra nhiều lượt tương tác với bàn phím trong một khoảng thời gian rất ngắn. Nếu việc thực hiện một sự kiện chính tốn nhiều tài nguyên (chẳng hạn như trong trường hợp phổ biến của các trường tự động hoàn thành, nơi các yêu cầu mạng được gửi đến một phần phụ trợ), bạn có thể chọn một trong hai cách sau:

  • Hãy cân nhắc việc loại bỏ các thao tác trùng lặp để giới hạn số lần thực thi lệnh gọi lại sự kiện trong một khoảng thời gian nhất định.
  • Sử dụng AbortController để huỷ các yêu cầu fetch đi, nhờ đó luồng chính không bị tắc nghẽn khi xử lý các lệnh gọi lại fetch. Lưu ý: bạn cũng có thể dùng thuộc tính signal của một thực thể AbortController để huỷ các sự kiện.

Một nguồn khác gây ra tình trạng tăng độ trễ đầu vào do các hoạt động tương tác chồng chéo có thể là các ảnh động tốn kém. Cụ thể, ảnh động trong JavaScript có thể kích hoạt nhiều lệnh gọi requestAnimationFrame, điều này có thể cản trở các lượt tương tác của người dùng. Để khắc phục vấn đề này, hãy sử dụng ảnh động CSS bất cứ khi nào có thể để tránh xếp hàng các khung hình ảnh động có khả năng tốn kém – nhưng nếu bạn làm điều này, hãy đảm bảo rằng bạn tránh ảnh động không được ghép để ảnh động chủ yếu chạy trên các luồng GPU và trình kết hợp, chứ không phải trên luồng chính.

Kết luận

Mặc dù độ trễ đầu vào có thể không chiếm phần lớn thời gian chạy các hoạt động tương tác của bạn, nhưng điều quan trọng là bạn phải hiểu rằng mọi phần của một hoạt động tương tác đều chiếm một khoảng thời gian mà bạn có thể giảm bớt. Nếu nhận thấy độ trễ đầu vào dài, thì bạn có thể giảm độ trễ này. Việc tránh các lệnh gọi lại bộ hẹn giờ định kỳ, chia nhỏ các tác vụ dài và nhận biết khả năng tương tác chồng chéo đều có thể giúp bạn giảm độ trễ đầu vào, dẫn đến khả năng tương tác nhanh hơn cho người dùng trang web của bạn.

Hình ảnh chính trên Unsplash, của Erik Mclean.