LeCodesdocs

События указателя и жесты

Общие объекты событий указателя. Каждая тапабельная поверхность в SDK — 3D-узлы, 2D-узлы, сцены и UI-элементы — доставляет одни и те же два события: click (отпускание над целью) и touchstart (нажатие, а также точка входа для жестов перетаскивания через ev.track()).

Обзор

TypeScript
// тап
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)
Note

click отправляется на отпускании в точке отпускания, независимо от активного трека — завершённое перетаскивание всё равно заканчивается click по тому, что под пальцем.

ClickEvent / TouchStartEvent

TypeScript
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() захватывает все последующие перемещения этого указателя и направляет их в твой обработчик, пока палец не поднимется:

TypeScript
ev.track({
  onMove(pos)   { },   // pos: { clientX, clientY, deltaX, deltaY }
  onEnd(pos)    { },   // палец поднят нормально
  onCancel()    { },   // жест отобрали (системный жест, teardown) — всегда прибирайся и здесь
  claim: 'pan-y',      // см. ниже
})
Note

deltaX / deltaYдельты на каждое перемещение — движение с предыдущего события перемещения, а не с момента нажатия. Для суммарного смещения перетаскивания храни стартовую точку сам: pos.clientX - ev.clientX.

claim — победа над скроллерами

Пока твой элемент находится внутри чего-то, что тоже хочет жест (UIScrollable, прокручиваемый экран), claim объявляет, какое движение принадлежит тебе:

TypeScript
claim: true         // забрать указатель безусловно (свободное 2D-перетаскивание: джойстик, рисование по канвасу)
claim: 'pan-x'      // забрать горизонтальное движение, вертикальное пусть начинает скролл
claim: 'pan-y'      // забрать вертикальное движение (нижние шторки)
claim: 'pan-up' | 'pan-down' | 'pan-left' | 'pan-right'   // только одно направление

Без claim скролл-контейнер, распознавший свой пан, может забрать указатель у тебя — ты получишь onCancel.

Подводные камни

TypeScript
// ✗ читать 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 })

Смотрите также