Trỏ về phía trước

Sérgio Gomes

Trước đây, việc trỏ vào những thông tin trên web vốn rất đơn giản. Bạn có chuột, bạn đã di chuyển nó đơn giản, đôi khi bạn chỉ cần nhấn vào các nút. Mọi thứ không phải là chuột được mô phỏng như một trò chơi và các nhà phát triển biết chính xác cần tin tưởng vào điều gì.

Tuy nhiên, đơn giản không nhất thiết có nghĩa là tốt. Theo thời gian, sản phẩm này ngày càng quan trọng là không phải mọi thứ đều là (hoặc giả vờ) là chuột: bạn có thể có bút cảm ứng áp lực và nhận biết nghiêng, cho sự tự do sáng tạo đáng kinh ngạc; bạn có thể sử dụng ngón tay, nên tất cả những gì bạn cần là thiết bị và bàn tay; và này, tại sao không dùng nhiều hơn một ngón tay khi đang ở đó?

Chúng tôi đã có các sự kiện chạm để giúp chúng tôi làm việc đó, nhưng chúng là một API hoàn toàn riêng biệt cho thao tác chạm, buộc bạn phải lập trình hai mô hình sự kiện riêng biệt nếu bạn muốn hỗ trợ cả chuột và thao tác chạm. Chrome 55 đi kèm với 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 bằng con trỏ cho trình duyệt, kết nối cảm ứng, bút và chuột với nhau thành 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.'));

Sau đây là danh sách tất cả sự kiện hiện có. Danh sách này khá quen thuộc nếu bạn bạn đã quen thuộc với sự kiện chuột:

pointerover Con trỏ đã nhập vào hộp giới hạn của phần tử. Thao tác 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 khi Sự kiện pointerdown cho các thiết bị không hoạt động.
pointerenter Tương tự như pointerover, nhưng không tạo bong bóng trò chuyện và tên người dùng các thành phần con cháu khác nhau. Thông tin chi tiết về thông số kỹ thuật.
pointerdown Con trỏ đã chuyển sang trạng thái nút đang hoạt động, với một nút là nhấn hoặc tiếp xúc đượ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 lỗi nên con trỏ rất ít khả năng sẽ phát ra bất kỳ sự kiện khác. Điều này có nghĩa là bạn nên huỷ mọi thao tác đang diễn ra và tiếp tục trở về trạng thái đầu vào bình thường.
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 một 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à tên người dùng các thành phần con cháu khác nhau. Thông tin chi tiết về thông số kỹ thuật.
gotpointercapture Phần tử đã nhận được người chụp con trỏ.
lostpointercapture Con trỏ đang được chụp đã bị phát hành.

Nhiều loại dữ liệu đầu vào

Nói chung, Sự kiện con trỏ cho phép bạn viết mã theo cách không phụ thuộc vào đầu vào, mà không cần đăng ký 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 chú ý đến sự khác biệt giữa các loại dữ liệu đầu vào, chẳng hạn như áp dụng khái niệm di chuột. Nếu muốn phân biệt các loại thiết bị đầu vào khác nhau, bạn có thể cung cấp riêng mã/chức năng cho các đầu vào khác nhau – tuy nhiên bạn có thể làm như vậy từ 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 PointerEvent . Ví dụ: nếu đang lập trình một ngăn điều hướng bên, bạn có thể có logic sau trên 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 các trình duyệt hỗ trợ thao tác chạm, một số cử chỉ nhất định được sử dụng để cuộn, thu phóng hoặc làm mới trang. Trong trường hợp sự kiện chạm, bạn vẫn sẽ nhận được các sự kiện khi các sự kiện 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 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 một sự kiện pointercancel để cho bạn biết rằng trình duyệt đã lấy quyền điều khiển 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 cho phép có hiệu suất tốt hơn theo mặc định, 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 touch-action thuộc tính CSS. Nếu bạn đặt chính sách này thành none trên một phần tử, tất cả các tuỳ chọn quảng cáo sẽ bị tắt các hành động do trình duyệt xác định đã bắt đầu trên phần tử đó. Tuy nhiên, có một số các 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 là 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 là cuộn theo chiều 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ỉ để 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ỉ để 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 cuộn theo chiều dọc. và chỉ di chuyển trang lên trên.
pan-down Trình duyệt chỉ được phép thực hiện thao tác mặc định cuộn theo chiều dọc. và chỉ dùng để di chuyển 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 dành một giờ khó chịu để gỡ lỗi mouseup bị hỏng cho đến khi bạn nhận ra rằng đó là do người dùng thả nút nằm 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ỉ có một mình tôi.

Tuy nhiên, cho đến nay vẫn chưa có cách thực sự hiệu quả để 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 này và lưu một số trạng thái trên ứng dụng của bạn để theo dõi mọi thứ. Đó không phải là giải pháp sạch nhất, đặc biệt nếu bạn đang tạo thành phần web và cố gắng giữ cho mọi thứ tốt đẹp bị tách biệt.

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

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 này, Sự kiện con trỏ được hỗ trợ trong Internet Explorer 11, Microsoft Edge, Chrome và Opera và được hỗ trợ một phần trong Firefox. Bạn có thể hãy tìm danh sách đã cập nhật tại caniuse.com.

Bạn có thể sử dụng polyfill Sự kiện con trỏ cho bổ sung thông tin còn thiếu. Ngoài ra, việc kiểm tra khả năng hỗ trợ của trình duyệt trong thời gian chạy là đơn giản:

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

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

Vì vậy, hãy dùng thử và cho chúng tôi biết bạn nghĩ gì!