Ukazatele událostí

Události ukazatele jsou moderním způsobem zpracování vstupu z různých ukazovacích zařízení, jako je myš, pero/stylus, dotyková obrazovka a tak dále.

Stručná historie

Udělejme si malý přehled, abyste porozuměli obecnému obrazu a místu událostí ukazatele mezi ostatními typy událostí.

  • Kdysi dávno, v minulosti, existovaly pouze události myši.

    Poté se rozšířila dotyková zařízení, zejména telefony a tablety. Aby existující skripty fungovaly, generovaly (a stále generují) události myši. Například klepnutí na dotykovou obrazovku vygeneruje mousedown . Dotyková zařízení tedy fungovala s webovými stránkami dobře.

    Dotyková zařízení však mají více možností než myš. Například je možné se dotknout více bodů najednou („multi-touch“). I když události myši nemají potřebné vlastnosti ke zpracování takových vícedotyků.

  • Byly tedy zavedeny dotykové události, jako je touchstart , touchend , touchmove , které mají vlastnosti specifické pro dotyk (nebudeme je zde podrobně popisovat, protože události ukazatele jsou ještě lepší).

    Přesto to nestačilo, protože existuje mnoho dalších zařízení, jako jsou pera, která mají své vlastní funkce. Také psaní kódu, který naslouchá událostem dotyku i myši, bylo těžkopádné.

  • K vyřešení těchto problémů byl představen nový standardní Ukazatel Events. Poskytuje jedinou sadu událostí pro všechny druhy ukazovacích zařízení.

Od nynějška je specifikace Pointer Events Level 2 podporována ve všech hlavních prohlížečích, zatímco novější Pointer Events Level 3 je v práci a je většinou kompatibilní s Pointer Events úrovně 2.

Pokud nevyvíjíte pro staré prohlížeče, jako je Internet Explorer 10 nebo Safari 12 nebo nižší, nemá smysl používat události myši nebo dotyku – můžeme přepnout na události ukazatele.

Pak bude váš kód dobře fungovat s dotykovými i myšími zařízeními.

To znamená, že existují některé důležité zvláštnosti, které by člověk měl znát, aby správně používal události ukazatele a vyhnul se překvapením. V tomto článku na ně upozorníme.

Typy událostí ukazatele

Události ukazatele jsou pojmenovány 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, za každých mouse<event> , je zde pointer<event> která hraje podobnou roli. Existují také 3 další události ukazatele, které nemají odpovídající mouse... protějšku, brzy je vysvětlíme.

Nahrazení mouse<event> s pointer<event> v našem kódu

Můžeme nahradit mouse<event> události s pointer<event> v našem kódu a očekávejte, že věci s myší budou i nadále fungovat správně.

„Magicky“ se zlepší i podpora dotykových zařízení. I když možná budeme muset přidat touch-action: none na některých místech v CSS. Tomu se budeme věnovat níže v části o pointercancel .

Vlastnosti události ukazatele

Události ukazatele mají stejné vlastnosti jako události myši, například clientX/Y , target , atd., plus některé další:

  • pointerId – jedinečný identifikátor ukazatele způsobujícího událost.

    Generováno prohlížečem. Umožňuje nám pracovat s více ukazateli, jako je dotyková obrazovka se stylusem a vícedotykové (příklady budou následovat).

  • pointerType – typ ukazovacího zařízení. Musí to být řetězec, jeden z:„myš“, „pero“ nebo „dotyk“.

    Tuto vlastnost můžeme použít k různé reakci na různé typy ukazatelů.

  • isPrimary – je true pro primární ukazatel (první prst ve vícedotykovém režimu).

Některá ukazovací zařízení měří kontaktní plochu a tlak, např. pro prst na dotykové obrazovce existují další vlastnosti:

  • width – šířka oblasti, kde se ukazatel (např. prst) dotýká zařízení. Tam, kde to není podporováno, např. u myši je to vždy 1 .
  • height – výška oblasti, kde se ukazatel dotýká zařízení. Pokud to není podporováno, je to vždy 1 .
  • pressure – tlak hrotu ukazovátka v rozsahu od 0 do 1. U zařízení, která nepodporují tlak, musí být buď 0.5 (stisknuto) nebo 0 .
  • tangentialPressure – normalizovaný tangenciální tlak.
  • tiltX , tiltY , twist – vlastnosti specifické pro pero, které popisují, jak je pero umístěno vzhledem k povrchu.

Tyto vlastnosti většina zařízení nepodporuje, takže se používají jen zřídka. Podrobnosti o nich najdete v případě potřeby ve specifikaci.

Vícedotykové

Jednou z věcí, kterou události myši zcela nepodporují, je vícedotykové ovládání:uživatel se může na svém telefonu nebo tabletu dotýkat na několika místech najednou nebo provádět speciální gesta.

Události ukazatele umožňují manipulaci s vícedotykovým ovládáním pomocí pointerId a isPrimary vlastnosti.

Co se stane, když se uživatel dotkne dotykové obrazovky na jednom místě a poté na ni položí další prst někde jinde:

  1. Prvním dotykem prstu:
    • pointerdown s isPrimary=true a některé pointerId .
  2. Pro druhý prst a více prstů (za předpokladu, že se první stále dotýká):
    • pointerdown s isPrimary=false a jiný pointerId pro každý prst.

Poznámka:pointerId není přiřazeno celému zařízení, ale každému dotyku prstu. Pokud použijeme 5 prstů k současnému dotyku obrazovky, máme 5 pointerdown události, každá se svými příslušnými souřadnicemi a jiným pointerId .

Události spojené s prvním prstem mají vždy isPrimary=true .

Můžeme sledovat více dotykových prstů pomocí jejich pointerId . Když se uživatel pohne a poté odstraní prst, dostaneme pointermove a pointerup události se stejným pointerId jako jsme měli v pointerdown .

Zde je ukázka, která zaznamenává pointerdown a pointerup události:

Poznámka:Abyste skutečně viděli rozdíl v pointerId/isPrimary, musíte používat zařízení s dotykovou obrazovkou, jako je telefon nebo tablet. . Pro jednodotyková zařízení, jako je myš, bude vždy stejné pointerId s isPrimary=true , pro všechny události ukazatele.

Událost:pointercancel

pointercancel událost se spustí, když probíhá interakce ukazatele, a pak se stane něco, co způsobí její přerušení, takže se již negenerují žádné další události ukazatele.

Takové příčiny jsou:

  • Hardware ukazovacího zařízení byl fyzicky deaktivován.
  • Změnila se orientace zařízení (tablet se otočil).
  • Prohlížeč se rozhodl zpracovat interakci sám, považoval ji za gesto myši nebo akci přiblížení a posunutí nebo něco jiného.

Předvedeme pointercancel na praktickém příkladu, abychom viděli, jak nás to ovlivňuje.

Řekněme, že implementujeme drag'n'drop pro míč, stejně jako na začátku článku Drag'n'drop s událostmi myši.

Zde je tok uživatelských akcí a odpovídajících událostí:

  1. Uživatel stiskne obrázek a začne přetahovat
    • pointerdown požáry události
  2. Potom začnou pohybovat ukazatelem (a tím přetáhnout obrázek)
    • pointermove požáry, možná několikrát
  3. A pak přijde překvapení! Prohlížeč má nativní podporu drag'n'drop pro obrázky, která spouští a přebírá proces drag'n'drop, čímž generuje pointercancel událost.
    • Prohlížeč nyní zvládá přetažení obrázku sám. Uživatel může dokonce přetáhnout obrázek míče z prohlížeče do svého poštovního programu nebo správce souborů.
    • Už žádné pointermove akce pro nás.

Problém je tedy v tom, že prohlížeč „unese“ interakci:pointercancel spustí se na začátku procesu přetažení a ne více pointermove události jsou generovány.

Zde je ukázka drag'n'drop s přihlášením událostí ukazatele (pouze up/down , move a cancel ) v textarea :

Chtěli bychom implementovat drag'n'drop sami, takže řekněme prohlížeči, aby to nepřebíral.

Zabraňte výchozí akci prohlížeče, abyste se vyhnuli pointercancel .

Musíme udělat dvě věci:

  1. Zabraňte nativnímu přetažení:
    • Můžeme to provést nastavením ball.ondragstart = () => false , jak je popsáno v článku Události přetažení myší.
    • To funguje dobře pro události myši.
  2. U dotykových zařízení existují další akce prohlížeče související s dotykem (kromě přetažení). Abyste se vyhnuli problémům i s nimi:
    • Zabraňte jim nastavením #ball { touch-action: none } v CSS.
    • Náš kód pak začne fungovat na dotykových zařízeních.

Poté, co to uděláme, budou události fungovat tak, jak bylo zamýšleno, prohlížeč proces neunese a nevyšle pointercancel .

Tato ukázka přidává tyto řádky:

Jak vidíte, žádné pointercancel neexistuje nic víc.

Nyní můžeme přidat kód, který skutečně posune míček, a naše přetažení bude fungovat pro myš a dotyková zařízení.

Zachycení ukazatele

Zachycení ukazatele je speciální funkcí událostí ukazatele.

Myšlenka je velmi jednoduchá, ale na první pohled se může zdát docela zvláštní, protože nic takového neexistuje pro žádný jiný typ události.

Hlavní metoda je:

  • elem.setPointerCapture(pointerId) – sváže události s daným pointerId na elem . Po volání všechny události ukazatele se stejným pointerId bude mít elem jako cíl (jako by se to stalo na elem ), bez ohledu na to, kde v dokumentu k nim skutečně došlo.

Jinými slovy elem.setPointerCapture(pointerId) znovu zacílí všechny následující události s daným pointerId na elem .

Vazba je odstraněna:

  • automaticky při pointerup nebo pointercancel dojde k událostem,
  • automaticky při elem je z dokumentu odstraněn
  • když elem.releasePointerCapture(pointerId) se nazývá.

K čemu je to dobré? Je čas podívat se na příklad ze skutečného života.

Zachycení ukazatele lze použít ke zjednodušení interakcí typu drag'n'drop.

Připomeňme si, jak lze implementovat vlastní posuvník popsaný v událostech Drag'n'Drop with mouse.

Můžeme vytvořit slider prvek představující proužek a „běžec“ (thumb ) uvnitř:

<div class="slider">
 <div class="thumb"></div>
</div>

Se styly to vypadá takto:

A zde je pracovní logika, jak byla popsána, po nahrazení událostí myši podobnými událostmi ukazatele:

  1. Uživatel stiskne posuvník thumbpointerdown spouštěče.
  2. Potom přesunou ukazatel – pointermove spouští a náš kód přesune thumb prvek podél.
    • …Jak se ukazatel pohybuje, může opustit posuvník thumb prvek, přejděte nad něj nebo pod něj. thumb by se měl pohybovat přísně vodorovně a zůstat zarovnaný s ukazatelem.

V řešení založeném na události myši ke sledování všech pohybů ukazatele, včetně toho, když se pohybuje nad/pod thumb , museli jsme přiřadit mousemove handler události na celém document .

To však není nejčistší řešení. Jedním z problémů je, že když uživatel přesune ukazatel po dokumentu, může to spustit obslužné rutiny událostí (jako je mouseover ) na některých dalších prvcích vyvolávat zcela nesouvisející funkce uživatelského rozhraní, a to nechceme.

Toto je místo, kde setPointerCapture přichází do hry.

  • Můžeme zavolat na číslo thumb.setPointerCapture(event.pointerId) v pointerdown manipulátor,
  • Poté budoucí události ukazatele do pointerup/cancel bude přesměrován na thumb .
  • Když pointerup dojde (přetažení dokončeno), vazba se automaticky odstraní, nemusíme se o to starat.

Takže i když uživatel přesune ukazatel po celém dokumentu, obslužné rutiny událostí budou volány na thumb . Nicméně vlastnosti souřadnic objektů události, jako je clientX/clientY bude stále správné – zachycení se týká pouze target/currentTarget .

Zde je základní kód:

thumb.onpointerdown = function(event) {
 // retarget all pointer events (until pointerup) to thumb
 thumb.setPointerCapture(event.pointerId);

 // start tracking pointer moves
 thumb.onpointermove = function(event) {
 // moving the slider: listen on the thumb, as all pointer events are retargeted to it
 let newLeft = event.clientX - slider.getBoundingClientRect().left;
 thumb.style.left = newLeft + 'px';
 };

 // on pointer up finish tracking pointer moves
 thumb.onpointerup = function(event) {
 thumb.onpointermove = null;
 thumb.onpointerup = null;
 // ...also process the "drag end" if needed
 };
};

// note: no need to call thumb.releasePointerCapture,
// it happens on pointerup automatically

Úplné demo:

V ukázce je také další prvek s onmouseover handler zobrazující aktuální datum.

Poznámka:při přetahování palcem můžete na tento prvek umístit ukazatel myši a jeho obslužný nástroj nedělá spoušť.

Takže přetahování je nyní bez vedlejších efektů díky setPointerCapture .

Zachycení ukazatele nám nakonec přináší dvě výhody:

  1. Kód je čistší, protože nemusíme přidávat/odebírat obslužné nástroje na celém document nic víc. Vazba se uvolní automaticky.
  2. Pokud jsou v dokumentu další obslužné rutiny události ukazatele, nebudou náhodně spuštěny ukazatelem, když uživatel přetahuje jezdec.

Ukazatel zachycující události

Pro úplnost je zde třeba zmínit ještě jednu věc.

Se zachycením ukazatele jsou spojeny dvě události:

  • gotpointercapture spustí se, když prvek používá setPointerCapture pro povolení snímání.
  • lostpointercapture spustí se při uvolnění zachycení:buď explicitně s releasePointerCapture zavolejte nebo automaticky na pointerup /pointercancel .

Shrnutí

Události ukazatele umožňují zpracovávat události myši, dotyku a pera současně pomocí jediného kódu.

Události ukazatele rozšiřují události myši. Můžeme nahradit mouse s pointer v názvech událostí a očekáváme, že náš kód bude nadále fungovat pro myš s lepší podporou pro ostatní typy zařízení.

Pro přetahování a složité dotykové interakce, které se prohlížeč může rozhodnout unést a zpracovat je sám – nezapomeňte zrušit výchozí akci u událostí a nastavit touch-action: none v CSS pro prvky, které používáme.

Další schopnosti událostí ukazatele jsou:

  • Podpora vícedotykového ovládání pomocí pointerId a isPrimary .
  • Vlastnosti specifické pro zařízení, například pressure , width/height a další.
  • Zachycení ukazatele:můžeme přesměrovat všechny události ukazatele na konkrétní prvek až do pointerup /pointercancel .

Od nynějška jsou události ukazatele podporovány ve všech hlavních prohlížečích, takže na ně můžeme bezpečně přejít, zvláště pokud nejsou potřeba IE10 a Safari 12. A dokonce i v těchto prohlížečích existují polyfilly, které umožňují podporu událostí ukazatele.