Skip to content

События и hit-тестирование

Сбор отделён от определения попаданий

Главный поток слушает pointer/wheel/touch с { passive: true }. События, связанные с прокруткой, лишь записывают дельту и метку времени в общий канал: никакого hit-тестирования и никакого setState.

Определение попаданий происходит в ядре: BVH по мировым AABB инкрементально поддерживается вместе со Scene (при изменении топологии перестраивается, при изменении только геометрии выполняется refit). После попадания строится путь root→target и возвращается оболочке по обратному потоку.

Property-тесты гарантируют, что BVH и наивная линейная реализация дают одинаковый результат: у оптимизированного пути всегда есть оракул для сравнения.

Трёхфазное распространение

Модель событий выровнена с DOM: перехват → цель → всплытие.

tsx
<container onClickCapture={(event) => log("outer capture", event.eventPhase)}>
  <container
    onPointerDown={(event) => {
      event.preventDefault();
      event.stopPropagation();
    }}
  />
</container>

Доступные обработчики: onPointerDown, onPointerUp, onPointerMove, onPointerCancel, onClick, onWheel, у каждого есть версия *Capture.

DoperEvent предоставляет target, currentTarget, eventPhase, логические координаты внутри canvas x/y, deltaX/deltaY, buttons, клавиши-модификаторы, preventDefault(), stopPropagation() и stopImmediatePropagation().

Проблема времени вызова preventDefault

Пассивный слушатель не может вызвать preventDefault(). Это вопрос корректности, который нужно решать явно, а не деталь, которую можно обойти.

Решение: области, где нужно подавить поведение по умолчанию (например, внутренняя прокручиваемая зона), ядро вычисляет заранее и синхронизирует в главный поток «прямоугольники непассивных областей». Главный поток переводит именно эти области на непассивные слушатели и вызывает preventDefault()синхронно, когда событие попадает внутрь. Поэтому нет гонки, зависящей от асинхронного ответа.

Границы семантики попаданий

Текущая семантика намеренно сужена, чтобы избежать неявного поведения:

  • При перекрывающихся попаданиях целью считается «нарисованный последним». Ни z-order, ни отключение попаданий через pointer-events, ни пропуск невидимых узлов пока не предоставляются. Введение любого из этих пунктов требует явного проектного решения.
  • Попадания по снимку кадра: все события одной пачки разрешаются относительно геометрии последнего закоммиченного кадра. Прокрутка внутри пачки, меняющая геометрию, повлияет на попадания только со следующего кадра — это обеспечивает атомарный откат пачки событий и детерминированное воспроизведение.
  • Ввод с клавиатуры идёт через протокол ввода редактирования и не маскируется под событие попадания.

В демонстрации событий в Playground видно журнал трёхфазного распространения в реальном времени.

Опубликовано под лицензией MIT