Події вказівника – це сучасний спосіб обробки даних, введених за допомогою різних вказівних пристроїв, таких як миша, перо/стилус, сенсорний екран тощо.
Коротка історія
Зробімо невеликий огляд, щоб ви зрозуміли загальну картину та місце вказівних подій серед інших типів подій.
-
Давним-давно, в минулому, існували лише події миші.
Потім набули поширення сенсорні пристрої, зокрема телефони та планшети. Щоб сценарії, які існують, працювали, вони генерували (і досі генерують) події миші. Наприклад, натискання на сенсорний екран генерує
mousedown. Тож сенсорні пристрої добре працювали з вебсторінками.Але сенсорні пристрої мають більше можливостей, ніж миша. Наприклад, можна торкнутися кількох точок одночасно (“multi-touch”). Хоча події миші не мають необхідних властивостей для обробки таких мультидотиків.
-
Тому були введені сенсорні події, такі як
touchstart,touchend,touchmove, які мають властивості, специфічні для дотику (тут ми не розглядаємо їх детально, тому що події вказівника ще кращі).Але все-таки цього було недостатньо, оскільки є багато інших пристроїв, наприклад ручок, які мають свої особливості. Крім того, код, який прослуховує як події дотику, так і миші, досить громіздкий.
-
Щоб розв’язати ці проблеми, було введено новий стандарт Pointer Events. Він забезпечує єдиний набір подій для всіх видів вказівних пристроїв.
На цей час специфікація Pointer Events Level 2 підтримується в усіх основних браузерах, тоді як новіша версія Pointer Events Level 3 знаходиться в розробці і в основному сумісна з Pointer Events другого рівня.
Якщо ви не розробляєте для старих браузерів, таких як Internet Explorer 10 або Safari 12 або старішої версії, то більше немає сенсу використовувати події миші чи дотику – ми можемо перейти на події вказівника.
Тоді ваш код буде добре працювати як із сенсорними приладами, так і з мишами.
Однак, є деякі важливі особливості, які потрібно знати, щоб правильно використовувати події вказівника та уникати несподіванок. Ми звернемо увагу на них у цій статті.
Типи подій вказівника
Події вказівника називаються аналогічно подіям миші:
| Події вказівника | Аналогічні події миші |
|---|---|
pointerdown |
mousedown |
pointerup |
mouseup |
pointermove |
mousemove |
pointerover |
mouseover |
pointerout |
mouseout |
pointerenter |
mouseenter |
pointerleave |
mouseleave |
pointercancel |
- |
gotpointercapture |
- |
lostpointercapture |
- |
Як ми бачимо, для кожного mouse<event> є pointer<event>, який відіграє подібну роль. Також є 3 додаткові події вказівника, які не мають відповідного аналога mouse..., ми пояснимо їх незабаром.
mouse<event> на pointer<event> у нашому кодіМи можемо замінити події mouse<event> на pointer<event> у нашому коді і очікувати, що вони працюватимуть нормально з мишею.
Підтримка сенсорних пристроїв також “магічним чином” покращиться. Хоча нам може знадобитися додати touch-action: none у деяких місцях CSS. Ми розглянемо це нижче в розділі про pointercancel.
Властивості події вказівника
Події вказівника мають ті самі властивості, що й події миші, такі як clientX/Y, target тощо, а також деякі інші:
-
pointerId– унікальний ідентифікатор вказівника, що спричиняє подію.Згенерований браузером. Дозволяє нам працювати з кількома вказівниками, такими як сенсорний екран зі стилусом і мультитач (приклади будуть далі).
-
pointerType– тип вказівного пристрою. Має бути рядком, одним із таких: “mouse”, “pen” або “touch”.Ми можемо використовувати цю властивість, щоб по-різному реагувати на різні типи вказівників.
-
isPrimary–trueдля основного вказівника (перший палець у мультитач).
Деякі вказівні пристрої вимірюють площу контакту та тиск, напр. для пальця на сенсорному екрані є додаткові властивості для цього:
width– ширина області, де вказівник (наприклад, палець) торкається пристрою. Якщо не підтримується, напр. для миші це завжди1.height– висота області, де вказівник торкається пристрою. Там, де не підтримується, завжди1.pressure– тиск вказівника в діапазоні від 0 до 1. Для пристроїв, які не підтримують тиск, має бути0,5(натиснутий) або0.tangentialPressure– нормалізований тангенційний тиск.tiltX,tiltY,twist– специфічні властивості пера, які описують, як воно розташовується відносно поверхні.
Ці властивості не підтримуються більшістю пристроїв, тому використовуються рідко. Ви можете знайти деталі про них у специфікації, якщо потрібно.
Мультитач
Однією з речей, яку події миші повністю не підтримують, є мультитач: користувач може торкатися в кількох місцях одночасно на своєму телефоні чи планшеті або виконувати спеціальні жести.
Події вказівника дозволяють обробляти мультитач за допомогою властивостей pointerId та isPrimary.
Ось що відбувається, коли користувач торкається сенсорного екрана в одному місці, а потім кладе інший палець в інше місце:
- При першому дотику пальцем:
pointerdownзisPrimary=trueта певнимpointerId.
- Для другого пальця та інших пальців (якщо перший досі торкається):
pointerdownзisPrimary=falseта іншимpointerIdдля кожного пальця.
Зверніть увагу: pointerId призначається не всьому пристрою, а кожному дотику пальця. Якщо ми використовуємо 5 пальців, щоб одночасно торкнутися екрана, у нас буде 5 подій pointerdown, кожна зі своїми відповідними координатами та іншим pointerId.
Події, пов’язані з першим пальцем, завжди мають значення isPrimary=true.
Ми можемо відстежувати кілька пальців, використовуючи їх pointerId. Коли користувач рухає, а потім прибирає палець, ми отримуємо події pointermove та pointerup з тим же pointerId, що й у pointerdown.
Ось демонстрація, яка реєструє події pointerdown та pointerup:
Зверніть увагу: щоб побачити різницю в pointerId/isPrimary, ви повинні використовувати пристрій із сенсорним екраном, наприклад телефон або планшет. Для пристроїв з одним дотиком, таких як миша, завжди буде однаковий pointerId з isPrimary=true для всіх подій вказівника.
Подія: pointercancel
Подія pointercancel спрацьовує, коли відбувається постійна взаємодія вказівника, а потім відбувається щось, що спричиняє її скасування.
Такими причинами є:
- Фізичне вимкнення обладнання вказівного пристрою.
- Зміна орієнтації пристрою (планшет повернуто).
- Рішення браузера обробляти взаємодію самостійно, вважаючи це жестом миші або дією масштабування та панорамування або чимось іншим.
Ми продемонструємо pointercancel на практичному прикладі, щоб побачити, як він впливає на нас.
Скажімо, ми впроваджуємо drag’n’drop для м’яча, як на початку статті Drag'n'Drop з подіями миші.
Ось хід дій користувача та відповідні події:
- Користувач натискає на зображення, щоб почати перетягування
- спрацьовує подія
pointerdown
- спрацьовує подія
- Потім він починає рухати вказівник (перетягуючи таким чином зображення)
pointermoveспрацьовує, можливо, кілька разів
- І тоді відбувається сюрприз! Браузер має вбудовану підтримку drag’n’drop для зображень, яка запускається та бере на себе процес drag’n’drop, таким чином генеруючи подію
pointercancel.- Тепер браузер самостійно обробляє перетягування зображення. Користувач може навіть перетягнути зображення м’яча з браузера, у свою поштову програму або файловий менеджер.
- Для нас більше немає подій
pointermove.
Таким чином, проблема полягає в тому, що браузер “викрадає” взаємодію: pointercancel запускається на початку процесу “перетягування” і події pointermove більше не генеруються.
Ось drag’n’drop демо з реєстрацією подій вказівника (лише up/down, move та cancel) у textarea:
Ми хотіли б реалізувати drag’n’drop самостійно, тому скажімо браузеру не брати це на себе.
Запобігання типовій дії браузера, щоб уникнути pointercancel.
Нам потрібно зробити дві речі:
- Запобігти нативному drag’n’drop:
- Ми можемо зробити це, встановивши
ball.ondragstart = () => false, як описано в статті Drag'n'Drop з подіями миші. - Це добре працює для подій миші.
- Ми можемо зробити це, встановивши
- Для сенсорних пристроїв існують інші дії браузера, пов’язані з дотиком (крім перетягування). Щоб уникнути проблем і з ними:
- Запобігти їм, встановивши
#ball { touch-action: none }у CSS - Тоді наш код почне працювати на сенсорних пристроях.
- Запобігти їм, встановивши
Після того, як ми це зробимо, події працюватимуть, як задумано, браузер не буде перехоплювати процес і не видаватиме pointercancel.
У цьому демо додаються такі рядки:
Як бачите, pointercancel більше немає.
Тепер ми можемо додати код для фактичного переміщення м’яча, і наш drag’n’drop працюватиме для пристроїв миші та сенсорних пристроїв.
Захоплення вказівника
Захоплення вказівника є особливою подією.
Ідея дуже проста, але спочатку може здатися досить дивною, оскільки нічого подібного для будь-якого іншого типу події не існує.
Основним методом є:
elem.setPointerCapture(pointerId)– зв’язує події із заданимpointerIdз