Trỏ về phía trước

Sérgio Gomes

Trỏ vào những thứ trên web trước đây khá đơn giản. Bạn có chuột, bạn di chuyển nó xung quanh, đôi khi bạn nhấn nút và thế là xong. Tất cả những thứ không phải là chuột đều được mô phỏng là một, và các nhà phát triển biết chính xác cần dựa vào gì.

Tuy nhiên, sự đơn giản không nhất thiết có nghĩa là tốt. Theo thời gian, không phải mọi thứ đều (hoặc giả vờ là) một con chuột.

Chúng tôi đã có sự kiện chạm được một thời gian để giúp làm việc đó, nhưng chúng là một API hoàn toàn riêng biệt dành riêng cho thao tác chạm, buộc bạn phải mã hoá hai mô hình sự kiện riêng biệt nếu muốn hỗ trợ cả chuột và thao tác chạm. Chrome 55 có một tiêu chuẩn mới hơn giúp hợp nhất cả hai mô hình: sự kiện con trỏ.

Mô hình sự kiện đơn lẻ

Sự kiện con trỏ hợp nhất mô hình nhập con trỏ cho trình duyệt, tập hợp thao tác chạm, bút và chuột vào một nhóm sự kiện duy nhất. Ví dụ:

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

Dưới đây là danh sách tất cả các sự kiện có sẵn, trông khá quen thuộc nếu bạn đã quen thuộc với các sự kiện chuột:

pointerover Con trỏ đã nhập vào hộp giới hạn của phần tử. Điều này xảy ra ngay lập tức đối với các thiết bị hỗ trợ tính năng di chuột hoặc trước sự kiện pointerdown đối với các thiết bị không hỗ trợ tính năng di chuột.
pointerenter Tương tự như pointerover, nhưng không tạo bong bóng trò chuyện và xử lý các thành phần con theo cách khác. Thông tin chi tiết về quy cách.
pointerdown Con trỏ đã chuyển sang trạng thái nút đang hoạt động, với một nút đang được nhấn hoặc một điểm tiếp xúc đang được thiết lập, tuỳ thuộc vào ngữ nghĩa của thiết bị đầu vào.
pointermove Con trỏ đã thay đổi vị trí.
pointerup Con trỏ đã rời khỏi trạng thái nút đang hoạt động.
pointercancel Đã xảy ra điều gì đó có nghĩa là con trỏ khó có khả năng sẽ phát ra thêm sự kiện nào. Điều này có nghĩa là bạn nên huỷ mọi hành động đang diễn ra và quay lại trạng thái đầu vào trung lập.
pointerout Con trỏ đã rời khỏi hộp giới hạn của phần tử hoặc màn hình. Ngoài ra, sau pointerup, nếu thiết bị không hỗ trợ tính năng di chuột.
pointerleave Tương tự như pointerout, nhưng không tạo bong bóng trò chuyện và xử lý các thành phần con theo cách khác. Thông tin chi tiết về quy cách.
gotpointercapture Phần tử đã nhận được lượt chụp con trỏ.
lostpointercapture Con trỏ đang được chụp đã được thả.

Các loại dữ liệu đầu vào

Nhìn chung, Sự kiện con trỏ cho phép bạn viết mã theo cách không phân biệt dữ liệu đầu vào mà không cần đăng ký các trình xử lý sự kiện riêng biệt cho các thiết bị đầu vào khác nhau. Tất nhiên, bạn vẫn cần lưu ý đến sự khác biệt giữa các loại dữ liệu đầu vào, chẳng hạn như liệu có áp dụng khái niệm di chuột hay không. Nếu muốn phân biệt các loại thiết bị đầu vào – có thể là để cung cấp mã/chức năng riêng cho các đầu vào khác nhau – tuy nhiên, bạn có thể làm như vậy trong cùng một trình xử lý sự kiện bằng cách sử dụng thuộc tính pointerType của giao diện PointerEvent. Ví dụ: nếu đang lập trình một ngăn điều hướng bên, bạn có thể sử dụng logic sau cho sự kiện pointermove:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

Thao tác mặc định

Trong trình duyệt có hỗ trợ cảm ứng, một số cử chỉ được dùng để cuộn, thu phóng hoặc làm mới trang. Trong trường hợp các sự kiện chạm, bạn sẽ vẫn nhận được các sự kiện khi những thao tác mặc định này đang diễn ra – ví dụ: touchmove sẽ vẫn được kích hoạt trong khi người dùng đang cuộn.

Với các sự kiện con trỏ, bất cứ khi nào một hành động mặc định như cuộn hoặc thu phóng được kích hoạt, bạn sẽ nhận được sự kiện pointercancel để cho bạn biết rằng trình duyệt đã kiểm soát con trỏ. Ví dụ:

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

Tốc độ tích hợp sẵn: Mô hình này theo mặc định cho phép đạt được hiệu suất tốt hơn so với các sự kiện chạm, trong đó bạn cần sử dụng trình nghe sự kiện thụ động để đạt được cùng mức độ phản hồi.

Bạn có thể ngăn trình duyệt nắm quyền kiểm soát bằng thuộc tính CSS touch-action. Việc đặt giá trị này thành none trên một phần tử sẽ vô hiệu hoá mọi thao tác do trình duyệt xác định đã bắt đầu trên phần tử đó. Tuy nhiên, có một số giá trị khác để kiểm soát chi tiết hơn, chẳng hạn như pan-x, để cho phép trình duyệt phản ứng với chuyển động trên trục x nhưng không phản ứng với trục y. Chrome 55 hỗ trợ các giá trị sau:

auto Mặc định; trình duyệt có thể thực hiện bất kỳ hành động mặc định nào.
none Trình duyệt không được phép thực hiện bất kỳ hành động mặc định nào.
pan-x Trình duyệt chỉ được phép thực hiện thao tác mặc định cuộn theo chiều ngang.
pan-y Trình duyệt chỉ được phép thực hiện thao tác mặc định cuộn dọc.
pan-left Trình duyệt chỉ được phép thực hiện thao tác mặc định cuộn theo chiều ngang và chỉ được dịch vụ xoay trang sang trái.
pan-right Trình duyệt chỉ được phép thực hiện thao tác mặc định cuộn theo chiều ngang và chỉ được dịch vụ xoay trang sang phải.
pan-up Trình duyệt chỉ được phép thực hiện thao tác mặc định là cuộn dọc và chỉ được kéo trang lên.
pan-down Trình duyệt chỉ được phép thực hiện thao tác cuộn dọc theo mặc định và chỉ được kéo trang xuống.
manipulation Trình duyệt chỉ được phép thực hiện các thao tác cuộn và thu phóng.

Chụp con trỏ

Bạn đã từng mất một giờ khó chịu để gỡ lỗi sự kiện mouseup bị hỏng, cho đến khi bạn nhận ra rằng đó là do người dùng đang thả nút bên ngoài mục tiêu nhấp chuột của bạn? Bạn không thấy đúng không? Được rồi, có lẽ chỉ mình tôi.

Tuy nhiên, cho đến nay vẫn chưa có một cách thực sự tốt để giải quyết vấn đề này. Chắc chắn rồi, bạn có thể thiết lập trình xử lý mouseup trên tài liệu và lưu một số trạng thái trên ứng dụng để theo dõi mọi thứ. Tuy nhiên, đó không phải là giải pháp rõ ràng nhất, đặc biệt nếu bạn đang xây dựng một thành phần web và cố gắng giữ cho mọi thứ tốt đẹp và tách biệt.

Sự kiện con trỏ mang đến một giải pháp tốt hơn nhiều: bạn có thể thu thập con trỏ để chắc chắn lấy được sự kiện pointerup đó (hoặc bất kỳ bạn bè nào khác khó nắm bắt).

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

Hỗ trợ trình duyệt

Tại thời điểm viết bài viết này, Sự kiện con trỏ được hỗ trợ trong Internet Explorer 11, Microsoft Edge, Chrome và Opera, đồng thời được hỗ trợ một phần trong Firefox. Bạn có thể tìm thấy danh sách mới nhất tại caniuse.com.

Bạn có thể sử dụng polyfill cho Sự kiện Con trỏ để lấp đầy các khoảng trống. Ngoài ra, việc kiểm tra tính năng hỗ trợ trình duyệt trong thời gian chạy cũng đơn giản hơn:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

Các sự kiện con trỏ là một ứng viên tuyệt vời cho tính năng nâng cao tăng dần: bạn chỉ cần sửa đổi các phương thức khởi chạy để thực hiện quy trình kiểm tra ở trên, thêm trình xử lý sự kiện con trỏ trong khối if và di chuyển trình xử lý sự kiện chuột/nhấn vào khối else.

Vì vậy, hãy thử khám phá ngay bây giờ và cho chúng tôi biết suy nghĩ của bạn!