События указателя и жесты
Общие объекты событий указателя. Каждая тапабельная поверхность в SDK — 3D-узлы, 2D-узлы, сцены и
UI-элементы — доставляет одни и те же два события: click (отпускание над целью) и touchstart
(нажатие, а также точка входа для жестов перетаскивания через ev.track()).
Обзор
// тап
ball.addEventListener('click', ev => {
console.log('tapped at', ev.clientX, ev.clientY)
})
// перетаскивание — захвати указатель на touchstart, затем следуй за ним
stick.onTouchStart(ev => {
const startX = ev.clientX, startY = ev.clientY
ev.track({
claim: true, // это наше перетаскивание — не отдавай его скроллу
onMove: p => place(p.clientX - startX, p.clientY - startY), // суммарное смещение от точки нажатия
onEnd: () => rest(),
onCancel: () => rest(),
})
})Где срабатывают события
| Поверхность | Как цель становится кликабельной | Слушать через |
|---|---|---|
3D Node |
есть аспект Shape (тело для пикинга) |
node.addEventListener('click' | 'touchstart', cb) |
3D Scene |
всегда (цель = задетый узел или null) |
scene.addEventListener(…) |
2D Node2D |
есть Shape2D + Physics2D/Trigger2D (тело для хит-теста) |
node.addEventListener(…) |
2D Scene2D |
всегда (цель = задетый узел или null) |
scene.addEventListener(…) или scene.pick(worldPoint) для ручных хит-тестов |
| UI | только UIButton, UIScreen, UIWidget |
.onClick(cb), .onTouchStart(cb) |
click отправляется на отпускании в точке отпускания, независимо от активного трека — завершённое
перетаскивание всё равно заканчивается click по тому, что под пальцем.
ClickEvent / TouchStartEvent
ev.clientX, ev.clientY // позиция указателя в логических px (экранное пространство)
ev.pointerId // стабильный id этого пальца
ev.target // задетый Node / Node2D или null (слушатели сцены)
ev.worldX, ev.worldY // только 2D-сцены: точка попадания в мировом пространстве (undefined в 3D)Перетаскивание: ev.track(handler)
Трекать может только TouchStartEvent. Вызов ev.track() захватывает все последующие перемещения
этого указателя и направляет их в твой обработчик, пока палец не поднимется:
ev.track({
onMove(pos) { }, // pos: { clientX, clientY, deltaX, deltaY }
onEnd(pos) { }, // палец поднят нормально
onCancel() { }, // жест отобрали (системный жест, teardown) — всегда прибирайся и здесь
claim: 'pan-y', // см. ниже
})deltaX / deltaY — дельты на каждое перемещение — движение с предыдущего события
перемещения, а не с момента нажатия. Для суммарного смещения перетаскивания храни стартовую точку
сам: pos.clientX - ev.clientX.
claim — победа над скроллерами
Пока твой элемент находится внутри чего-то, что тоже хочет жест (UIScrollable, прокручиваемый
экран), claim объявляет, какое движение принадлежит тебе:
claim: true // забрать указатель безусловно (свободное 2D-перетаскивание: джойстик, рисование по канвасу)
claim: 'pan-x' // забрать горизонтальное движение, вертикальное пусть начинает скролл
claim: 'pan-y' // забрать вертикальное движение (нижние шторки)
claim: 'pan-up' | 'pan-down' | 'pan-left' | 'pan-right' // только одно направлениеБез claim скролл-контейнер, распознавший свой пан, может забрать указатель у тебя — ты получишь
onCancel.
Подводные камни
// ✗ читать deltaX как «расстояние от места, где началось касание»
onMove: p => knob.style({ transform: `translate(${p.deltaX}px, 0)` }) // дёргается около нуля
// ✓ дельты — на каждое перемещение; накапливай или считай разницу со стартовой точкой
onMove: p => knob.style({ transform: `translate(${p.clientX - ev.clientX}px, 0)` })
// ✗ очистка только в onEnd — забранный системный жест её пропустит
ev.track({ onEnd: stop })
// ✓ onCancel срабатывает вместо onEnd, когда жест отбирают
ev.track({ onEnd: stop, onCancel: stop })Смотрите также
- 2D-физика и пикинг — что делает
Node2Dкликабельным. - 3D-физика и формы — тела для пикинга на 3D-узлах.
- Кнопки и поля ввода —
UIButton.onClick, отклик на нажатие. - Input — опрос клавиатуры.