Události ukazatele jsou modernÃm způsobem, jak zpracovat vstup z různorodých ukazovacÃch zaÅÃzenÃ, napÅÃklad myÅ¡, pero/stylus, doteková obrazovka a podobnÄ.
Krátká historie
UdÄlejme si malý pÅehled, abyste porozumÄli obecnému obrazu a mÃstu událostà ukazatele mezi ostatnÃmi druhy událostÃ.
-
PÅed dlouhou dobou v minulosti existovaly jen události myÅ¡i.
Pak se rozÅ¡ÃÅila doteková zaÅÃzenÃ, zvláštÄ mobilnà telefony a tablety. Aby na nich fungovaly již existujÃcà skripty, generovala (a stále generujÃ) události myÅ¡i, napÅÃklad dotek na dotekové obrazovce vyvolá
mousedown. Doteková zaÅÃzenà tedy s webovými stránkami fungovala dobÅe.Jenže doteková zaÅÃzenà majà vÃce možnostà než myÅ¡. Je napÅÃklad možné dotknout se vÃce bodů najednou (âmultidotekâ). Události myÅ¡i vÅ¡ak nemajà nezbytné vlastnosti k tomu, aby takové multidoteky zpracovaly.
-
Byly tedy zavedeny dotekové události, napÅ.
touchstart,touchend,touchmove, které obsahujà vlastnosti specifické pro dotek (nebudeme je tady podrobnÄ probÃrat, jelikož vlastnosti ukazatele jsou jeÅ¡tÄ lepÅ¡Ã).Stále to vÅ¡ak nestaÄilo, protože existuje mnoho dalÅ¡Ãch zaÅÃzenÃ, napÅ. pera, která majà své vlastnà schopnosti. KromÄ toho bylo nepohodlné psát kód, který naslouchá jak dotekovým událostem, tak událostem myÅ¡i.
-
K vyÅeÅ¡enà tÄchto problémů byl zaveden nový standard událostà ukazatele, který poskytuje jedinou sadu událostà pro vÅ¡echny druhy ukazovacÃch zaÅÃzenÃ.
V souÄasnosti je ve vÅ¡ech významných prohlÞeÄÃch podporována specifikace událostà ukazatele 2. úrovnÄ, zatÃmco novÄjšà specifikace událostà ukazatele 3. úrovnÄ je ve vývoji a je s událostmi ukazatele 2. úrovnÄ pÅevážnÄ kompatibilnÃ.
Pokud nevyvÃjÃte pro staré prohlÞeÄe, napÅ. Internet Explorer 10, Safari 12 nebo starÅ¡Ã, nenà již nadále důvod použÃvat události myÅ¡i nebo dotekové události â můžeme pÅejÃt na události ukazatele.
Váš kód pak bude správnÄ fungovat s dotekovými zaÅÃzenÃmi i s myÅ¡Ã.
PÅi tom vÅ¡em vÅ¡ak existujà důležité zvláštnosti, které byste mÄli znát, abyste použÃvali události ukazatele správnÄ a vyhnuli se pÅekvapenÃm. ZmÃnÃme se o nich v tomto Älánku.
Typy událostà ukazatele
Události ukazatele se nazývajà podobnÄ jako události myÅ¡i:
| Událost ukazatele | Podobná událost myši |
|---|---|
pointerdown |
mousedown |
pointerup |
mouseup |
pointermove |
mousemove |
pointerover |
mouseover |
pointerout |
mouseout |
pointerenter |
mouseenter |
pointerleave |
mouseleave |
pointercancel |
- |
gotpointercapture |
- |
lostpointercapture |
- |
Jak vidÃme, pro každou mouse<událost> existuje pointer<událost>, která hraje podobnou roli. NavÃc existujà tÅi dalšà události ukazatele, které nemajà odpovÃdajÃcà protÄjÅ¡ek mouse.... Brzy je vysvÄtlÃme.
mouse<událost> za pointer<událost> v naÅ¡em kóduV naÅ¡em kódu můžeme nahradit události mouse<událost> událostmi pointer<událost> a oÄekávat, že vÅ¡e bude s myšà fungovat správnÄ.
RovnÄž se âmagickyâ zlepšà podpora dotekových zaÅÃzenÃ, aÄkoli na nÄkterých mÃstech v CSS budeme asi muset pÅidat touch-action: none. Probereme to dále v podkapitole o pointercancel.
Vlastnosti událostà ukazatele
Události ukazatele majà tytéž vlastnosti jako události myÅ¡i, napÅ. clientX/Y, target atd., plus nÄkteré dalÅ¡Ã:
-
pointerIdâ unikátnà identifikátor ukazatele, který vyvolal událost.Generováno prohlÞeÄem. UmožÅuje nám zpracovat vÃce ukazatelů, napÅÃklad dotekovou obrazovku se stylusem a multidotek (pÅÃklady budou následovat).
-
pointerTypeâ typ ukazovacÃho zaÅÃzenÃ. Musà to být ÅetÄzec, a to jeden z následujÃcÃch:"mouse","pen"nebo"touch".Tuto vlastnost můžeme použÃt k tomu, abychom na různé druhy ukazatelů reagovali různÄ.
-
isPrimaryâ jetruepro primárnà ukazatel (prvnà prst pÅi multidoteku).
NÄkterá ukazovacà zaÅÃzenà mÄÅà plochu a tlak doteku, napÅ. prstu na dotekové obrazovce. Pro nÄ existujà následujÃcà vlastnosti:
widthâ Å¡ÃÅka plochy, kde se ukazatel (napÅ. prst) dotýká zaÅÃzenÃ. Když nenà podporována, napÅ. u myÅ¡i, je vždy1.heightâ výška plochy, kde se ukazatel dotýká zaÅÃzenÃ. Když nenà podporována, je vždy1.pressureâ tlak doteku ukazatele v rozsahu od 0 do 1. U zaÅÃzenÃ, která tlak nepodporujÃ, musà být buÄ0.5(stisknuto), nebo0.tangentialPressureâ normalizovaný tangenciálnà tlak.tiltX,tiltY,twistâ vlastnosti specifické pro pero, které popisujÃ, jak je pero umÃstÄno vzhledem k povrchu.
VÄtÅ¡ina zaÅÃzenà tyto vlastnosti nepodporuje, takže se použÃvajà jen vzácnÄ. Budete-li je potÅebovat, podrobnosti o nich naleznete ve specifikaci.
Multidotek
Jedna z vÄcÃ, které události myÅ¡i vůbec nepodporujÃ, je multidotek: uživatel se může svého telefonu nebo tabletu dotknout na nÄkolika mÃstech najednou nebo provádÄt speciálnà gesta.
Události ukazatele umožÅujà zpracovánà multidoteku pomocà vlastnostà pointerId a isPrimary.
Když se uživatel dotkne dotekové obrazovky na jednom mÃstÄ a pak na ni položà dalšà prst nÄkde jinde, stane se následujÃcÃ:
- PÅi prvnÃm doteku prstu:
pointerdownsisPrimary=truea nÄjakýmpointerId.
- PÅi druhém a dalÅ¡Ãch dotecÃch prstu (za pÅedpokladu, že prvnà prst se stále dotýká):
pointerdownsisPrimary=falsea odlišnýmpointerIdpro každý prst.
ProsÃme vÅ¡imnÄte si, že pointerId nenà pÅiÅazeno celému zaÅÃzenÃ, ale každému dotýkajÃcÃmu se prstu. Jestliže se souÄasnÄ dotkneme obrazovky pÄti prsty, dostaneme pÄt událostà pointerdown, každá bude mÃt své vlastnà souÅadnice a odliÅ¡ný pointerId.
Události spojené s prvnÃm prstem budou mÃt vždy isPrimary=true.
Pomocà pointerId můžeme sledovat vÃce dotýkajÃcÃch se prstů. Když uživatel pÅesune prst a pak jej zvedne, dostaneme události pointermove a pointerup se stejným pointerId, jaký jsme mÄli v pointerdown.
NásledujÃcà demo loguje události pointerdown a pointerup:
ProsÃme vÅ¡imnÄte si, že abyste skuteÄnÄ vidÄli rozdÃl v pointerId/isPrimary, musÃte použÃvat dotekové zaÅÃzenÃ, napÅÃklad mobilnà telefon nebo tablet. U jednodotekových zaÅÃzenÃ, jakým je myÅ¡, budou vÅ¡echny události ukazatele mÃt poÅád stejné pointerId a isPrimary=true.
Událost pointercancel
Událost pointercancel se spustÃ, když probÃhá interakce s ukazatelem a pak se stane nÄco, co způsobà jejà ukonÄenÃ, takže žádné dalšà události ukazatele se nevygenerujÃ.
Takové pÅÃpady jsou:
- Dotekové zaÅÃzenà bylo fyzicky odpojeno.
- ZmÄnila se orientace zaÅÃzenà (tablet byl otoÄen).
- ProhlÞeÄ se rozhodl, že tuto interakci zpracuje sám, protože ji považuje za gesto myÅ¡i, akci zoom-and-pan nebo nÄco jiného.
PÅedvedeme si pointercancel na praktickém pÅÃkladu, abychom vidÄli, jak nás ovlivnÃ.
Dejme tomu, že implementujeme pÅetahovánà mÃÄe, podobnÄ jako na zaÄátku Älánku PÅetahovánà pomocà událostà myÅ¡i.
PrůbÄh uživatelských akcà a odpovÃdajÃcÃch událostà je následujÃcÃ:
- Uživatel se dotkne obrázku, aby zaÄal pÅetahovat.
- spustà se událost
pointerdown
- spustà se událost
- Pak zaÄne pohybovat ukazatelem (a tedy pÅetahovat obrázek)
- spustà se událost
pointermove, zÅejmÄ nÄkolikrát
- spustà se událost
- A pak následuje pÅekvapenÃ! ProhlÞeÄ má svou nativnà podporu pÅetahovánà obrázků, která se spustà a pÅevezme proces pÅetahovánÃ, ÄÃmž vygeneruje událost
pointercancel.- ProhlÞeÄ bude nynà zpracovávat pÅetahovánà obrázku sám. Uživatel může dokonce pÅetáhnout obrázek mÃÄe mimo prohlÞeÄ, tÅeba do svého poÅ¡tovnÃho programu nebo správce souborů.
- Žádné dalšà události
pointermovese pro nás nekonajÃ.
Problém tedy je v tom, že prohlÞeÄ se âzmocnÃâ interakce: na zaÄátku procesu âpÅetahovánÃâ se spustà pointercancel a žádné dalšà události pointermove se nevygenerujÃ.
Následuje demo pÅetahovánà s logovánÃm událostà ukazatele (jen up/down, move a cancel) do textarea:
My bychom ovÅ¡em chtÄli implementovat své vlastnà pÅetahovánÃ. ÅeknÄme tedy prohlÞeÄi, aby je nepÅebÃral.
Abyste se vyhnuli pointercancel, zakažte standardnà akce prohlÞeÄe.
MusÃme udÄlat následujÃcà dvÄ vÄci:
- Zakázat aktivaci nativnÃho pÅetahovánÃ:
- Můžeme to udÄlat nastavenÃm
mÃÄ.ondragstart = () => false, tak, jak je popsáno v kapitole PÅetahovánà pomocà událostà myÅ¡i. - To funguje správnÄ pro události myÅ¡i.
- Můžeme to udÄlat nastavenÃm
- Pro doteková zaÅÃzenà existujà i jiné akce prohlÞeÄe týkajÃcà se doteku (kromÄ pÅetahovánÃ). Abychom se vyhnuli problémům i s nimi:
- Zakážeme je nastavenÃm
#mÃÄ { touch-action: none }v CSS. - Pak bude náš kód fungovat na dotekových zaÅÃzenÃch.
- Zakážeme je nastavenÃm
Když to udÄláme, budou události fungovat tak, jak zamýšlÃme. ProhlÞeÄ se nezmocnà procesu a nevyvolá pointercancel.
V následujÃcÃm demu jsou tyto Åádky pÅidány:
Jak vidÃte, dalšà pointercancel se nekoná.
Nynà můžeme pÅidat kód, který bude skuteÄnÄ hýbat mÃÄem, a naÅ¡e pÅetahovánà bude správnÄ fungovat pro myÅ¡ i pro doteková zaÅÃzenÃ.
Zachycenà ukazatele
Zachycenà ukazatele je speciálnà prvek událostà ukazatele.
Jeho myÅ¡lenka je velmi jednoduchá, ale na prvnà pohled se může zdát trochu zvláštnÃ, jelikož pro události žádného jiného druhu nic podobného neexistuje.
Hlavnà metodou je:
elem.setPointerCapture(pointerId)â naváže události se zadanýmpointerIdnaelem. Po tomto volánà budou vÅ¡echny události ukazatele se stejnýmpointerIdmÃt cÃlelem(jako by se staly naelem), bez ohledu na to, kde v dokumentu se opravdu staly.
Jinými slovy, elem.setPointerCapture(pointerId) pÅesmÄruje vÅ¡echny následné události se zadaným pointerId na elem.
Vazba bude odstranÄna:
- automaticky, když nastane událost
pointerupnebopointercancel, - automaticky, když je z dokumentu odstranÄn
elem, - když je volána
elem.releasePointerCapture(pointerId).
K Äemu je to nynà dobré? Nastal Äas ukázat pÅÃklad ze skuteÄného života.
Zachycenà ukazatele můžeme použÃt ke zjednoduÅ¡enà pÅetahovánà a podobných interakcÃ.
VzpomeÅme si, jak můžeme implementovat vlastnà posuvnÃk, popsaný v kapitole PÅetahovánà pomocà událostà myÅ¡i.
Můžeme vytvoÅit element posuvnÃk, který bude pÅedstavovat pás a âÅ¡oupátkoâ (Å¡oupátko) uvnitÅ:
<div class="posuvnÃk">
<div class="šoupátko"></div>
</div>
S použitÃm stylů vypadá následovnÄ:
Po náhradÄ událostà myÅ¡i podobnými událostmi ukazatele je logika fungovánà následujÃcÃ, jak bylo popsáno:
- Uživatel se dotkne šoupátka
Å¡oupátkoâ spustà sepointerdown. - Pak uživatel pÅesune ukazatel â spustà se
pointermovea náš kód pÅesune elementÅ¡oupátko.- â¦Když se ukazatel pÅesunuje, může opustit element
Å¡oupátkoa pÅesunout se nad nebo pod nÄ. Pak by seÅ¡oupátkomÄlo posunovat výhradnÄ vodorovnÄ a zůstat zarovnané s ukazatelem.
- â¦Když se ukazatel pÅesunuje, může opustit element
V ÅeÅ¡enà založeném na událostech myÅ¡i jsme pro sledovánà vÅ¡ech pohybů ukazatele, vÄetnÄ jeho pÅemÃstÄnà nad nebo pod Å¡oupátko, museli pÅiÅadit celému document handler události mousemove.
To vÅ¡ak nenà to nejÄistšà ÅeÅ¡enÃ. JednÃm z jeho problémů je, že když uživatel posunuje ukazatelem po dokumentu, může spustit handlery událostà (napÅ. mouseover) na jiných elementech a vyvolat zcela nesouvisejÃcà funkcionalitu UI, což nechceme.
Na tomto mÃstÄ pÅicházà do hry setPointerCapture.
- V handleru
pointerdownmůžeme volatšoupátko.setPointerCapture(událost.pointerId). - Pak se budoucà události ukazatele až do
pointerup/cancelpÅesmÄrujà naÅ¡oupátko. - Když dojde k
pointerup(pÅetahovánà skonÄÃ), vazba se automaticky odstranà a my se o ni nemusÃme starat.
I když tedy uživatel bude pohybovat ukazatelem po celém dokumentu, handlery událostà se budou volat na Å¡oupátko. PÅesto budou souÅadnicové vlastnosti objektů událostÃ, napÅ. clientX/clientY, stále správnÄ â zachycenà má vliv pouze na target/currentTarget.
Zde je nástin kódu:
šoupátko.onpointerdown = function(událost) {
// pÅesmÄruje vÅ¡echny události ukazatele (až do pointerup) na Å¡oupátko
šoupátko.setPointerCapture(událost.pointerId);
// zaÄneme sledovat pohyby ukazatele
šoupátko.onpointermove = function(událost) {
// pohyb posuvnÃku: nasloucháme na Å¡oupátku, jelikož vÅ¡echny události ukazatele jsou pÅesmÄrovány na nÄj
let nováLevá = událost.clientX - posuvnÃk.getBoundingClientRect().left;
šoupátko.style.left = nováLevá + 'px';
};
// pÅi zvednutà ukazatele ukonÄÃme sledovánà jeho pohybu
šoupátko.onpointerup = function(událost) {
šoupátko.onpointermove = null;
šoupátko.onpointerup = null;
// ...také zpracujeme âkonec pÅetahovánÃâ, je-li to nutné
};
};
// poznámka: nemusÃme volat Å¡oupátko.releasePointerCapture,
// pÅi pointerup k nÄmu dojde automaticky
Celé demo:
V demu je navÃc dalšà element s handlerem onmouseover, který zobrazuje aktuálnà datum.
ProsÃme vÅ¡imnÄte si, že když pÅetahujete Å¡oupátko, můžete se nad tento element pÅesunout a jeho handler se nespustÃ.
PÅetahovánà tedy dÃky setPointerCapture nemá žádné vedlejšà efekty.
Nakonec nám tedy zachycenà ukazatele dává dvÄ výhody:
- Kód je ÄistÅ¡Ã, neboÅ¥ již nemusÃme pÅidávat a odstraÅovat handlery na celém
document. Vazba se odstranà automaticky. - Pokud jsou v dokumentu jiné handlery událostà ukazatele, ukazatel je nemůže neúmyslnÄ spustit, když uživatel posunuje posuvnÃkem.
Události pro zachycenà ukazatele
Pro úplnost se zmÃnÃme jeÅ¡tÄ o jedné vÄci.
Se zachycenÃm ukazatele jsou spojeny dvÄ události:
gotpointercapturese spustÃ, když element použijesetPointerCapture, aby umožnil zachycenÃ.lostpointercapturese spustÃ, když je zachycenà odstranÄno: buÄ výslovnÄ volánÃmreleasePointerCapture, nebo automaticky pÅipointerup/pointercancel.
ShrnutÃ
Události ukazatele nám umožÅujà zpracovávat události myÅ¡i, dotekových zaÅÃzenà a pera souÄasnÄ, stejnou Äástà kódu.
Události ukazatele rozÅ¡iÅujà události myÅ¡i. V názvech událostà můžeme nahradit mouse za pointer a oÄekávat, že náš kód bude i nadále fungovat pro myÅ¡ a bude mÃt lepšà podporu zaÅÃzenà jiných druhů.
PÅi pÅetahovánà a složitých dotekových interakcÃch, kdy se prohlÞeÄ může rozhodnout zmocnit se jich a zpracovávat je sám, nezapomeÅte zakázat standardnà akce na událostech nastavenÃm touch-action: none v CSS pro elementy, kterých se to týká.
Dalšà schopnosti událostà ukazatele jsou:
- Podpora multidoteku pomocÃ
pointerIdaisPrimary. - Vlastnosti specifické pro nÄkterá zaÅÃzenÃ, napÅ.
pressure,width/heighta jiné. - Zachycenà ukazatele: můžeme vÅ¡echny události ukazatele pÅesmÄrovat na specifický element až do
pointerup/pointercancel.
V souÄasnosti jsou události ukazatele podporovány ve vÅ¡ech významných prohlÞeÄÃch, takže na nÄ můžete bezpeÄnÄ pÅejÃt, zvláštÄ pokud nepotÅebujete IE10- a Safari 12-. A i pro tyto prohlÞeÄe existujà polyfilly, které umožÅujà podporu událostà ukazatele.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)