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

Mariko Kosaka

Sắp có dữ liệu đầu vào trong Trình tổng hợp

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

Nhập sự kiện từ góc nhìn của trình duyệt

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

Khi cử chỉ của người dùng như chạm trên màn hình xảy ra, quy trình của trình duyệt sẽ nhận cử chỉ đầu tiên. Tuy nhiên, quy trình của trình duyệt chỉ biết được vị trí diễn ra cử chỉ đó vì quy trình kết xuất xử lý nội dung bên trong một thẻ. Vì vậy, quy trình của trình duyệt sẽ gửi loại sự kiện (như touchstart) và toạ độ của sự kiện đến quy trình kết xuất. Quy trình kết xuất sẽ xử lý sự kiện một cách phù 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 nhập
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 các 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 cách trình tổng hợp có thể xử lý cuộn mượt mà bằng cách kết hợp các lớp được tạo điểm ảnh. Nếu không có trình nghe sự kiện đầu vào nào được đính kèm vào trang, thì luồng Trình tổng hợp 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 trình tổng hợp sẽ tìm hiểu xem sự kiện có cần được xử lý như thế nào không?

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

Do 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, chuỗi trình tổng hợp đánh dấu một vùng trên trang có đính kèm các trình xử lý sự kiện là "Khu vực không thể cuộn nhanh". Bằng cách 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 tới luồng chính nếu sự kiện xảy ra ở khu vực đó. 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 mới mà không cần chờ luồng chính.

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

Lưu ý khi bạn ghi 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ì bong bóng sự kiện, 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 nhiệm vụ dựa trên mục tiêu sự kiện. Có thể bạn đã nhìn thấy hoặc viết mã như dưới đây.

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 tính công thái học của mẫu uỷ quyền sự kiện này rất hấp dẫn. Tuy nhiên, nếu bạn nhìn vào mã này từ góc nhìn của trình duyệt, thì toàn bộ trang hiện được đánh dấu là một khu vực có thể cuộn không nhanh. Điều này có nghĩa là ngay cả khi ứng dụng không quan tâm đến dữ liệu đầu vào từ một số phần nhất định của trang, thì luồng trình kết hợp phải giao tiếp với luồng chính và chờ đợi mỗi khi có sự kiện nhập. Do đó, khả năng cuộn mượt mà của trình tổng hợp bị đánh bại.

khu vực không thể cuộn nhanh toàn trang
Hình 4: Sơ đồ dữ liệu đầu vào được mô tả liên quan đến vùng không thể cuộn nhanh, bao gồm toàn bộ trang

Để giảm thiểu điều này xảy ra, bạn có thể truyền các tuỳ chọn passive: true vào trình nghe sự kiện. Lệnh 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à kết 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 trong trang web có phần được đặt cố định ở dạng cuộn ngang

Giả sử bạn có một hộp trên trang mà bạn muốn giới hạn hướng cuộn chỉ thành cuộn ngang.

Việc sử dụng tuỳ chọn passive: true trong sự kiện con trỏ có nghĩa là quá trình cuộn trang có thể mượt mà, nhưng thao tác cuộn 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

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

Khi luồng trình tổng 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 truy cập để tìm mục tiêu sự kiện. Quy trình kiểm thử lượt truy cập 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 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 của chúng ta làm mới màn hình 60 lần một giây và cách chúng ta cần bắt kịp tần suất để tạo ảnh động mượt mà. Đối với đầu vào, một thiết bị màn hình cảm ứng thông thường phân phối sự kiện chạm 60 đến 120 lần mỗi giây và một chuột thông thường phân phối sự kiện 100 lần trong một 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 của 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 trong một giây, thì sự kiện này có thể kích hoạt quá nhiều lượt kiểm tra lượt truy cập và thực thi JavaScript so với tốc độ làm mới của màn hình.

sự kiện chưa được lọc
Hình 7: Sự kiện tràn ngập dòng thời gian kết xuất khung hình khiến trang bị giật

Để giảm thiểu số lệnh gọi quá mức đến luồng chính, Chrome sẽ kết 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 gửi cho đến ngay trước requestAnimationFrame tiếp theo.

sự kiện được kết hợp
Hình 8: Tiến trình như trước nhưng sự kiện được hợp nhất và bị hoãn

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

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

Đối với hầu hết ứng dụng web, các sự kiện kết hợp là đủ để mang lại trải nghiệm tốt cho người dùng. Tuy nhiên, nếu bạn đang xây dựng những tính năng như ứng dụng vẽ và đặt đường dẫn dựa trên toạ độ touchmove, thì có thể bạn sẽ bị mất các toạ độ giữa để vẽ một đường thẳng. 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 được kết hợp đó.

getCoalescedEvents
Hình 9: Đường dẫn cử chỉ chạm mượt ở bên trái, đường dẫn giới hạn được 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 video này, chúng ta đã đề cập đến cơ chế hoạt động bên trong của một trình duyệt web. Nếu bạn chưa từng nghĩ về lý do tại sao DevTools đề xuất thêm {passive: true} vào trình xử lý sự kiện hoặc lý do bạn có thể viết thuộc tính async trong thẻ tập lệnh của mì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 đó để cung cấp trải nghiệm web nhanh hơn và mượt mà hơn.

Sử dụng Lighthouse

Nếu bạn muốn cải thiện mã của mình cho 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 và những gì cần cải thiện. Đọc qua danh sách các cuộc kiểm tra cũng cung cấp cho bạn ý tưởng về những loại nội dung mà trình duyệt quan tâm.

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

Các điều chỉnh về hiệu suất có thể khác nhau tuỳ theo trang web. Vì vậy, bạn cần phải đo lường hiệu suất của trang web và quyết định điều gì 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 của bạn

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à 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 tính năng sẽ đảm bảo một số hành vi nhất định của ứng dụng và giúp bạn không mắc lỗi. Ví dụ: Nếu muốn đảm bảo ứng dụng 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 bạn bật sync-script: 'none', JavaScript chặn trình phân tích cú pháp sẽ không thực thi được. Việc 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 gần như chỉ quan tâm đến cách viết mã và những yếu tố 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 suy nghĩ về cách trình duyệt lấy mã 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 cách thức cung cấp trải nghiệm web tốt hơn cho người dùng. Đối xử tốt với trình duyệt bằng cách sắp xếp mã của chúng tôi, sẽ cải thiện trải nghiệm người dùng. Hy vọng bạn cùng tôi tham gia vào hành trình làm quen với trình duyệt!

Xin chân thành cảm ơn những người đã xem xét các bản nháp đầu tiên của loạt bài này, bao gồm (nhưng không giới hạn ở): Alex Russell, Paul Ireland, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda}, Kinuko Yasuda},

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