Tìm hiểu chi tiết về trình duyệt web hiện đại (phần 4)

Mariko Kosaka

Dữ liệu đầu vào đang chuyển đến Trình kết hợp

Đây là phần cuối trong loạt blog gồm 4 phần tìm hiểu bên trong Chrome; tìm hiểu cách Chrome xử lý mã của chúng tôi để hiển thị trang web. Trong bài đăng trước, chúng ta đã xem xét quá trình kết xuất và tìm hiểu về trình kết hợp. Trong bài đăng này, chúng ta sẽ xem xét cách trình tổng hợp cho phép tương tác mượt mà khi người dùng nhập dữ liệu.

Sự kiện đầu vào theo quan điểm của trình duyệt

Khi nghe thấy cụm từ "sự kiện đầu vào", bạn có thể chỉ nghĩ đến việc nhập vào hộp văn bản hoặc nhấp chuột, nhưng theo quan điểm của trình duyệt, dữ liệu đầu vào có nghĩa là bất kỳ cử chỉ nào của người dùng. Thao tác cuộn bằng con lăn chuột là một sự kiện đầu vào và thao tác chạm hoặc di chuột cũng là một sự kiện đầu vào.

Khi cử chỉ của người dùng như chạm vào màn hình xảy ra, quá trình trình duyệt sẽ là quá trình nhận được cử chỉ đó lúc đầu. Tuy nhiên, quy trình trình duyệt chỉ nhận biết được vị trí xảy ra cử chỉ đó vì nội dung bên trong thẻ được xử lý bằng quy trình kết xuất. Vì vậy, quy trình trình duyệt sẽ gửi loại sự kiện (chẳng hạn như touchstart) và toạ độ của sự kiện đó đến quy trình trình kết xuất. Quy trình kết xuất xử lý sự kiện một cách thích hợp bằng cách tìm mục tiêu sự kiện và chạy trình nghe sự kiện được đính kèm.

sự kiện đầu vào
Hình 1: Sự kiện đầu vào được định tuyến thông qua quy trình của trình duyệt đến quy trình kết xuất

Trình tổng hợp nhận sự kiện nhập

Hình 2: Khung nhìn di chuột qua các lớp trang

Trong bài đăng trước, chúng ta đã xem xét cách trình tổng hợp có thể xử lý thao tác cuộn mượt mà bằng cách tổng hợp các lớp đã chuyển đổi đường quét. Nếu không có trình nghe sự kiện đầu vào nào được đính kèm vào trang, luồng Compositor có thể tạo một khung tổng hợp mới hoàn toàn độc lập với luồng chính. Nhưng nếu một số trình nghe sự kiện được đính kèm vào trang thì sao? Luồng của trình tổng hợp sẽ tìm hiểu xem sự kiện đó có cần được xử lý hay không?

Tìm hiểu về vùng có thể cuộn không nhanh

Vì việc chạy JavaScript là công việc của luồng chính, nên khi một trang được kết hợp, luồng trình kết hợp sẽ đánh dấu một vùng của trang có trình xử lý sự kiện được đính kèm là "Vùng không cuộn nhanh". Khi có thông tin này, luồng trình kết hợp có thể đảm bảo gửi sự kiện đầu vào đến luồng chính nếu sự kiện xảy ra trong vùng đó. Nếu sự kiện đầu vào đến từ bên ngoài khu vực này, thì luồng trình kết hợp sẽ tiếp tục kết hợp khung hình mới mà không cần chờ luồng chính.

khu vực không thể cuộn nhanh bị giới hạn
Hình 3: Sơ đồ mô tả dữ liệu đầu vào cho vùng có thể cuộn không nhanh

Hãy lưu ý khi bạn viết trình xử lý sự kiện

Một mẫu xử lý sự kiện phổ biến trong quá trình phát triển web là uỷ quyền sự kiện. Vì các sự kiện được tạo thành bong bóng, nên bạn có thể đính kèm một trình xử lý sự kiện ở phần tử trên cùng và uỷ quyền các tác vụ dựa trên mục tiêu sự kiện. Bạn có thể đã thấy hoặc viết mã như bên dưới.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Vì bạn chỉ cần viết một trình xử lý sự kiện cho tất cả các phần tử, nên mẫu uỷ quyền sự kiện này rất hấp dẫn. Tuy nhiên, nếu bạn xem mã này từ quan điểm của trình duyệt, thì giờ đây, toàn bộ trang sẽ được đánh dấu là vùng không thể cuộn nhanh. Điều này có nghĩa là ngay cả khi ứng dụng của bạn không quan tâm đến dữ liệu đầu vào từ một số phần nhất định của trang, luồng trình tổng hợp vẫn phải giao tiếp với luồng chính và đợi luồng đó mỗi khi có sự kiện đầu vào xuất hiện. Do đó, tính năng cuộn mượt mà của trình kết hợp bị vô hiệu hoá.

toàn bộ trang không thể cuộn nhanh
Hình 4: Sơ đồ mô tả dữ liệu đầu vào cho vùng có thể cuộn không nhanh bao phủ toàn bộ trang

Để giảm thiểu tình trạng này, bạn có thể truyền các tuỳ chọn passive: true trong trình nghe sự kiện. Điều này cho trình duyệt biết rằng bạn vẫn muốn nghe sự kiện trong luồng chính, nhưng trình tổng hợp cũng có thể tiếp tục và tổng hợp khung mới.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Kiểm tra xem sự kiện có thể huỷ hay không

cuộn trang
Hình 5: Một trang web có một phần trang được cố định để cuộn theo chiều ngang

Hãy tưởng tượng bạn có một hộp trong trang mà bạn chỉ muốn giới hạn hướng cuộn ở chế độ cuộn theo chiều ngang.

Việc sử dụng tuỳ chọn passive: true trong sự kiện con trỏ có nghĩa là thao tác cuộn trang có thể diễn ra mượt mà, nhưng thao tác cuộn theo chiều dọc có thể đã bắt đầu vào thời điểm bạn muốn preventDefault để giới hạn hướng cuộn. Bạn có thể kiểm tra điều này bằng cách sử dụng phương thức event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Ngoài ra, bạn có thể sử dụng quy tắc CSS như touch-action để loại bỏ hoàn toàn trình xử lý sự kiện.

#area {
  touch-action: pan-x;
}

Tìm mục tiêu sự kiện

kiểm thử nhấn
Hình 6: Luồng chính xem bản ghi vẽ hỏi nội dung được vẽ trên điểm x.y

Khi luồng trình kết hợp gửi một sự kiện đầu vào đến luồng chính, điều đầu tiên cần chạy là kiểm thử lượt nhấn để tìm mục tiêu sự kiện. Kiểm thử lượt nhấn sử dụng dữ liệu bản ghi sơn được tạo trong quá trình kết xuất để tìm hiểu nội dung nằm bên dưới toạ độ điểm xảy ra sự kiện.

Giảm thiểu việc gửi sự kiện đến luồng chính

Trong bài đăng trước, chúng ta đã thảo luận về cách màn hình thông thường làm mới màn hình 60 lần/giây và cách chúng ta cần bắt kịp nhịp độ để tạo ảnh động mượt mà. Đối với phương thức nhập, thiết bị có màn hình cảm ứng thông thường sẽ thực hiện sự kiện chạm từ 60 đến 120 lần/giây, còn một con chuột thông thường sẽ thực hiện sự kiện 100 lần/giây. Sự kiện đầu vào có độ trung thực cao hơn so với khả năng làm mới màn hình.

Nếu một sự kiện liên tục như touchmove được gửi đến luồng chính 120 lần/giây, thì sự kiện đó có thể kích hoạt quá nhiều lượt kiểm thử lượt nhấn và thực thi JavaScript so với tốc độ làm mới màn hình.

sự kiện chưa lọc
Hình 7: Các sự kiện tràn ngập tiến trình khung hình gây ra hiện tượng giật trang

Để giảm thiểu các lệnh gọi quá mức đến luồng chính, Chrome sẽ tổng hợp các sự kiện liên tục (chẳng hạn như wheel, mousewheel, mousemove, pointermove, touchmove) và trì hoãn việc điều phối cho đến ngay trước requestAnimationFrame tiếp theo.

sự kiện liên kết
Hình 8: Cùng một dòng thời gian như trước, nhưng sự kiện đang được hợp nhất và bị trì hoãn

Mọi sự kiện riêng biệt như keydown, keyup, mouseup, mousedown, touchstarttouchend sẽ được gửi ngay lập tức.

Sử dụng getCoalescedEvents để nhận các sự kiện trong khung

Đối với hầu hết các ứng dụng web, các sự kiện được kết hợp là đủ để cung cấp trải nghiệm người dùng tốt. Tuy nhiên, nếu đang xây dựng các ứng dụng như vẽ và đặt đường dẫn dựa trên toạ độ touchmove, bạn có thể mất toạ độ giữa các toạ độ để vẽ một đường kẻ mượt mà. Trong trường hợp đó, bạn có thể sử dụng phương thức getCoalescedEvents trong sự kiện con trỏ để nhận thông tin về các sự kiện đã liên kết đó.

getCoalescedEvents
Hình 9: Đường dẫn cử chỉ chạm mượt mà ở bên trái, đường dẫn giới hạn hợp nhất ở bên phải
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Các bước tiếp theo

Trong loạt bài này, chúng tôi đã đề cập đến hoạt động bên trong trình duyệt web. Nếu bạn chưa từng nghĩ đến lý do DevTools đề xuất thêm {passive: true} vào trình xử lý sự kiện hoặc lý do bạn có thể ghi thuộc tính async trong thẻ tập lệnh, tôi hy vọng loạt bài này sẽ làm sáng tỏ lý do trình duyệt cần những thông tin đó để mang lại trải nghiệm web nhanh và mượt mà hơn.

Sử dụng Lighthouse

Nếu bạn muốn mã của mình phù hợp với trình duyệt nhưng không biết bắt đầu từ đâu, thì Lighthouse là một công cụ chạy quy trình kiểm tra mọi trang web và cung cấp cho bạn báo cáo về những gì đang được thực hiện đúng cách và những gì cần cải thiện. Việc đọc qua danh sách kiểm tra cũng giúp bạn biết được những điều mà trình duyệt quan tâm.

Tìm hiểu cách đo lường hiệu suất

Các biện pháp điều chỉnh hiệu suất có thể khác nhau tuỳ theo trang web. Vì vậy, điều quan trọng là bạn phải đo lường hiệu suất của trang web và quyết định biện pháp nào phù hợp nhất với trang web của mình. Nhóm Công cụ của Chrome cho nhà phát triển có một vài hướng dẫn về cách đo lường hiệu suất của trang web.

Thêm Chính sách tính năng vào trang web

Nếu bạn muốn thực hiện thêm một bước, Chính sách tính năng là một tính năng mới trên nền tảng web có thể là một biện pháp bảo vệ cho bạn khi xây dựng dự án. Việc bật chính sách về tính năng sẽ đảm bảo hoạt động nhất định của ứng dụng và giúp bạn không mắc sai lầm. Ví dụ: Nếu muốn đảm bảo ứng dụng của mình không bao giờ chặn quá trình phân tích cú pháp, bạn có thể chạy ứng dụng trên chính sách tập lệnh đồng bộ. Khi sync-script: 'none' được bật, JavaScript chặn trình phân tích cú pháp sẽ không được thực thi. Điều này giúp ngăn mọi mã của bạn chặn trình phân tích cú pháp và trình duyệt không cần lo lắng về việc tạm dừng trình phân tích cú pháp.

Tóm tắt

cảm ơn bạn

Khi bắt đầu xây dựng trang web, tôi hầu như chỉ quan tâm đến cách viết mã và những gì giúp tôi làm việc hiệu quả hơn. Những điều đó rất quan trọng, nhưng chúng ta cũng nên nghĩ đến cách trình duyệt lấy mã chúng ta viết. Các trình duyệt hiện đại đã và đang tiếp tục đầu tư vào việc mang lại trải nghiệm web tốt hơn cho người dùng. Việc tạo điều kiện cho trình duyệt bằng cách sắp xếp mã sẽ giúp cải thiện trải nghiệm người dùng. Tôi hy vọng bạn sẽ cùng tôi thực hiện hành trình làm hài lòng các trình duyệt!

Cảm ơn rất nhiều những người đã xem xét các bản nháp ban đầu của loạt bài viết này, bao gồm (nhưng không giới hạn ở): Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Nasko Oskov và Charlie Reis.

Bạn có thích loạt bài này không? Nếu bạn có câu hỏi hoặc ý kiến đề xuất cho bài đăng trong tương lai, tôi muốn lắng nghe ý kiến của bạn trong phần bình luận bên dưới hoặc gửi @kosamari trên Twitter.