W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
指針事件(Pointer Events)是一種用于處理來自各種輸入設(shè)備(例如鼠標(biāo)、觸控筆和觸摸屏等)的輸入信息的現(xiàn)代化解決方案。
讓我們先做一個簡短的概覽,以便你對指針事件及其在其它事件類型中所處位置有個粗略認識。
后來,觸屏設(shè)備開始普及,尤其是手機和平板電腦。為了使現(xiàn)有的腳本仍能正常工作,它們生成(現(xiàn)在仍生成)鼠標(biāo)事件。例如,輕觸屏幕就會生成 mousedown
事件。因此,觸摸設(shè)備可以很好地與網(wǎng)頁配合使用。
但是,觸摸設(shè)備比鼠標(biāo)具有更多的功能。例如,我們可以同時觸控多點(多點觸控)。然而,鼠標(biāo)事件并沒有相關(guān)屬性來處理這種多點觸控。
touchstart
、touchend
和 touchmove
,它們具有特定于觸摸的屬性(這里不再贅述這些特性,因為指針事件更加完善)。不過這還是不夠完美。因為很多其他輸入設(shè)備(如觸控筆)都有自己的特性。而且同時維護兩份分別處理鼠標(biāo)事件和觸摸事件的代碼,顯得有些笨重了。
目前,各大主流瀏覽器已經(jīng)支持了 Pointer Events Level 2 標(biāo)準(zhǔn),版本更新的 Pointer Events Level 3 已經(jīng)發(fā)布,并且大多數(shù)情況下與 Pointer Events Level 2 兼容。
因此,除非你寫的代碼需要兼容舊版本的瀏覽器,例如 IE 10 或 Safari 12 或更低的版本,否則無需繼續(xù)使用鼠標(biāo)事件或觸摸事件 —— 我們可以使用指針事件。
這樣,你的代碼就可以在觸摸設(shè)備和鼠標(biāo)設(shè)備上都能正常工作了。
話雖如此,指針事件仍然有一些重要的奇怪特性,你應(yīng)當(dāng)對它們有所了解以正確使用指針事件,并避免一些意料之外的錯誤。我們將在本文中對它們進行介紹。
指針事件的命名方式和鼠標(biāo)事件類似:
指針事件 | 類似的鼠標(biāo)事件 |
---|---|
pointerdown
|
mousedown
|
pointerup
|
mouseup
|
pointermove
|
mousemove
|
pointerover
|
mouseover
|
pointerout
|
mouseout
|
pointerenter
|
mouseenter
|
pointerleave
|
mouseleave
|
pointercancel
|
- |
gotpointercapture
|
- |
lostpointercapture
|
- |
不難發(fā)現(xiàn),每一個 mouse<event>
都有與之相對應(yīng)的 pointer<event>
。同時還有 3 個額外的事件沒有相應(yīng)的 mouse...
,我們會在稍后詳細解釋它們。
在代碼中用 ?
pointer<event>
? 替換 ?mouse<event>
?我們可以把代碼中的
mouse<event>
都替換成pointer<event>
,程序仍然正常兼容鼠標(biāo)設(shè)備。
替換之后,程序?qū)τ|屏設(shè)備的支持會“魔法般”地提升。但是,我們可能需要在 CSS 中的某些地方添加
touch-action: none
。我們會在下文的pointercancel
一節(jié)中描述這里面的細節(jié)。
指針事件具備和鼠標(biāo)事件完全相同的屬性,包括 clientX/Y
和 target
等,以及一些其他屬性:
pointerId
? —— 觸發(fā)當(dāng)前事件的指針唯一標(biāo)識符。瀏覽器生成的。使我們能夠處理多指針的情況,例如帶有觸控筆和多點觸控功能的觸摸屏(下文會有相關(guān)示例)。
pointerType
? —— 指針的設(shè)備類型。必須為字符串,可以是:“mouse”、“pen” 或 “touch”。我們可以使用這個屬性來針對不同類型的指針輸入做出不同響應(yīng)。
isPrimary
? —— 當(dāng)指針為首要指針(多點觸控時按下的第一根手指)時為 ?true
?。有些指針設(shè)備會測量接觸面積和點按壓力(例如一根手指壓在觸屏上),對于這種情況可以使用以下屬性:
width
? —— 指針(例如手指)接觸設(shè)備的區(qū)域的寬度。對于不支持的設(shè)備(如鼠標(biāo)),這個值總是 ?1
?。height
? —— 指針(例如手指)接觸設(shè)備的區(qū)域的長度。對于不支持的設(shè)備,這個值總是 ?1
?。pressure
? —— 觸摸壓力,是一個介于 0 到 1 之間的浮點數(shù)。對于不支持壓力檢測的設(shè)備,這個值總是 ?0.5
?(按下時)或 ?0
?。tangentialPressure
? —— 歸一化后的切向壓力(tangential pressure)。tiltX, tiltY, twist
? —— 針對觸摸筆的幾個屬性,用于描述筆和屏幕表面的相對位置。大多數(shù)設(shè)備都不支持這些屬性,因此它們很少被使用。如果你需要使用它們,可以在 規(guī)范文檔 中查看更多有關(guān)它們的詳細信息。
多點觸控(用戶在手機或平板上同時點擊若干個位置,或執(zhí)行特殊手勢)是鼠標(biāo)事件完全不支持的功能之一。
指針事件使我們能夠通過 pointerId
和 isPrimary
屬性的幫助,能夠處理多點觸控。
當(dāng)用戶用一根手指觸摸觸摸屏的某個位置,然后將另一根手指放在該觸摸屏的其他位置時,會發(fā)生以下情況:
pointerdown
? 事件觸發(fā),?isPrimary=true
?,并且被指派了一個 ?pointerId
?。pointerdown
? 事件觸發(fā),?isPrimary=false
?,并且每一個觸摸都被指派了不同的 ?pointerId
?。請注意:pointerId
不是分配給整個設(shè)備的,而是分配給每一個觸摸的。如果 5 根手指同時觸摸屏幕,我們會得到 5 個 pointerdown
事件和相應(yīng)的坐標(biāo)以及 5 個不同的 pointerId
。
和第一個觸摸相關(guān)聯(lián)的事件總有 isPrimary=true
。
利用 pointerId
,我們可以追蹤多根正在觸摸屏幕的手指。當(dāng)用戶移動或抬起某根手指時,我們會得到和 pointerdown
事件具有相同 pointerId
的 pointermove
或 pointerup
事件。
這是一個記錄 pointerdown
和 pointerup
事件的演示:
請注意:你使用的必須是一個多點觸控設(shè)備(如平板或手機)才能在 pointerId/isPrimary
中看到區(qū)別。對于使用鼠標(biāo)這樣的單點觸控設(shè)備,所有指針事件都會具有相同的 pointerId
和 isPrimary=true
屬性。
pointercancel
事件將會在一個正處于活躍狀態(tài)的指針交互由于某些原因被中斷時觸發(fā)。也就是在這個事件之后,該指針就不會繼續(xù)觸發(fā)更多事件了。
導(dǎo)致指針中斷的可能原因如下:
我們會用一個實際例子來闡釋 pointercancel
的影響。
例如,我們想要實現(xiàn)一個像 鼠標(biāo)拖放事件 中開頭提到的那樣的一個對球的拖放操作。
用戶的操作流和對應(yīng)的事件如下:
pointerdown
? 事件觸發(fā)pointermove
? 事件觸發(fā),可能觸發(fā)多次pointercancel
? 事件。pointermove
? 事件了。這里的問題就在于瀏覽器”劫持“了這一個互動操作:在“拖放”過程開始時觸發(fā)了 pointercancel
事件,并且不再有 pointermove
事件會被生成。
這里是拖放示例的演示,并且在拖放過程中,指針事件(只包含 up/down
、move
和 cancel
)的觸發(fā)會被記錄在 textarea
中:
我們想要實現(xiàn)自己的拖放操作,所以讓我們來看看如何告訴瀏覽器不要接管拖放操作。
阻止瀏覽器的默認行為來防止 pointercancel
觸發(fā)。
我們需要做兩件事:
ball.ondragstart = () => false
? 來實現(xiàn)這一需求。#ball { touch-action: none }
? 來阻止它們。經(jīng)過上述操作,事件將會按照我們預(yù)期的方式觸發(fā),瀏覽器不會劫持拖放過程,也不會觸發(fā) pointercancel
事件。
這個演示增加了以下幾行:
可以看到,pointercancel
事件不再被觸發(fā)。
現(xiàn)在我們就可以添加讓球的位置移動的代碼了,并且我們的代碼對鼠標(biāo)和觸控設(shè)備都有效。
指針捕獲(Pointer capturing)是針對指針事件的一個特性。
這個想法很簡單,但是乍一看可能感覺很奇怪,因為在其他任何事件類型中都沒有這種東西。
主要的方法是:
elem.setPointerCapture(pointerId)
? —— 將給定的 ?pointerId
? 綁定到 ?elem
?。在調(diào)用之后,所有具有相同 ?pointerId
? 的指針事件都將 ?elem
? 作為目標(biāo)(就像事件發(fā)生在 ?elem
? 上一樣),無論這些 ?elem
? 在文檔中的實際位置是什么。換句話說,elem.setPointerCapture(pointerId)
將所有具有給定 pointerId
的后續(xù)事件重新定位到 elem
。
綁定會在以下情況下被移除:
pointerup
? 或 ?pointercancel
? 事件出現(xiàn)時,綁定會被自動地移除。elem
? 被從文檔中移除后,綁定會被自動地移除。elem.releasePointerCapture(pointerId)
? 被調(diào)用,綁定會被移除。那么,它有什么用?我們一起來看一個實際的例子吧。
指針捕獲可以被用于簡化拖放類的交互。
讓我們來回憶一下在 鼠標(biāo)拖放事件 中提到的,如何實現(xiàn)一個自定義滑動條。
我們可以創(chuàng)建一個帶有條形圖的、并且內(nèi)部有一個“滑塊”(thumb
)的滑動條元素(slider
):
<div class="slider">
<div class="thumb"></div>
</div>
添加樣式后的效果如下:
用指針事件替換鼠標(biāo)事件后的實現(xiàn)邏輯:
thumb
? —— ?pointerdown
? 事件被觸發(fā)。pointermove
? 事件被觸發(fā),我們讓移動事件只作用在 ?thumb
? 上。thumb
? 元素,移動到 ?thumb
? 之上或之下的位置。而 ?thumb
? 應(yīng)該嚴格在水平方向上移動,并與指針保持對齊。
在基于鼠標(biāo)事件實現(xiàn)的方案中,要跟蹤指針的所有移動,包括指針移動到 thumb
之上或之下的位置時,我們必須在整個文檔 document
上分配 mousemove
事件處理程序。
不過,這并不是一個沒有副作用的解決方案。其中的一個問題就是,指針在文檔周圍的移動可能會引起副作用,在其他元素上觸發(fā)事件處理程序(例如 mouseover
)并調(diào)用其他元素上與滑動條不相關(guān)的功能,這不是我們預(yù)期的效果。
這就是 setPointerCapture
適用的場景。
pointerdown
? 事件的處理程序中調(diào)用 ?thumb.setPointerCapture(event.pointerId)
?,pointerup/cancel
? 之前發(fā)生的所有指針事件都會被重定向到 ?thumb
? 上。pointerup
? 發(fā)生時(拖動完成),綁定會被自動移除,我們不需要關(guān)心它。因此,即使用戶在整個文檔上移動指針,事件處理程序也將僅在 thumb
上被調(diào)用。盡管如此,事件對象的坐標(biāo)屬性,例如 clientX/clientY
仍將是正確的 —— 捕獲僅影響 target/currentTarget
。
主要代碼如下:
thumb.onpointerdown = function(event) {
// 把所有指針事件(pointerup 之前發(fā)生的)重定向到 thumb
thumb.setPointerCapture(event.pointerId);
// 開始跟蹤指針的移動
thumb.onpointermove = function(event) {
// 移動滑動條:在 thumb 上監(jiān)聽即可,因為所有指針事件都被重定向到了 thumb
let newLeft = event.clientX - slider.getBoundingClientRect().left;
thumb.style.left = newLeft + 'px';
};
// 當(dāng)結(jié)束(pointerup)時取消對指針移動的跟蹤
thumb.onpointerup = function(event) {
thumb.onpointermove = null;
thumb.onpointerup = null;
// ...這里還可以處理“拖動結(jié)束”相關(guān)的邏輯
};
};
// 注意:無需調(diào)用 thumb.releasePointerCapture,
// 它會在 pointerup 時被自動調(diào)用
在這個 demo 中還有一個元素,當(dāng)它的 onmouseover
處理程序被觸發(fā)時會顯示當(dāng)前的時間。
請注意:當(dāng)你拖動滑塊的時候,鼠標(biāo)可能會懸停在這個元素上,它的 onmouseover
處理程序不會被觸發(fā)。
借助于 setPointerCapture
,現(xiàn)在拖動滑塊不會再產(chǎn)生副作用了。
言而總之,指針捕獲為我們帶來了兩個好處:
document
? 上添加/移除處理程序。綁定會被自動釋放。完整起見,這里還需要提及一個知識點。
還有兩個與指針捕獲相關(guān)的事件:
gotpointercapture
? 會在一個元素使用 ?setPointerCapture
? 來啟用捕獲后觸發(fā)。lostpointercapture
? 會在捕獲被釋放后觸發(fā):其觸發(fā)可能是由于 ?releasePointerCapture
? 的顯式調(diào)用,或是 ?pointerup/pointercancel
? 事件觸發(fā)后的自動調(diào)用。指針事件允許我們通過一份代碼,同時處理鼠標(biāo)、觸摸和觸控筆事件。
指針事件是鼠標(biāo)事件的拓展。我們可以在事件名稱中用 pointer
替換 mouse
來讓我們的代碼既能繼續(xù)支持鼠標(biāo),也能更好地支持其他類型的設(shè)備。
對于瀏覽器可能會決定進行劫持并自行處理的拖放和復(fù)雜的觸控交互 —— 請記住取消事件的默認操作,并在 CSS 中為涉及到的元素設(shè)置 touch-action: none
。
指針事件還額外具備以下能力:
pointerId
? 和 ?isPrimary
? 的多點觸控支持。pressure
? 和 ?width/height
? 等。pointerup/pointercancel
? 之前的所有指針事件重定向到一個特定的元素。目前,指針事件已經(jīng)被各大主流瀏覽器支持,尤其是如果不需要支持 IE10 和 Safari 12 以下的版本,我們可以放心地使用它們。不過即便是針對這些老式瀏覽器,也可以通過 polyfill 來讓它們支持指針事件。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: