PathLogs UI
English

Hooks

useDragScroll

Прокрутка протяжкой мыши с инерцией и растворением краёв.

Зажали ленту, потянули — она едет за курсором и по инерции доезжает после отпускания. Как панорамирование в графе, только для обычного контейнера с прокруткой.

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14

Зажмите и потяните. Отпустите на движении — лента доедет по инерции. Края растворяются там, где спрятан контент.

Хук возвращает ref-колбэк — его достаточно повесить на контейнер. Классы прокрутки (overflow-x-auto и прочее) задаёте вы: хук отвечает за поведение, а не за раскладку.

Настройки

axis"x" | "y" | "both"

по умолчанию: "x"

Ось прокрутки. Клавиши поперёк оси не перехватываются — их ждёт страница.

momentumboolean

по умолчанию: true

Инерция после броска. Автоматически выключается при prefers-reduced-motion.

keyboardboolean

по умолчанию: false

Лента встаёт в порядок табуляции и слушает стрелки, Page и Home/End — но только когда фокус на ней самой, а не на элементе внутри.

enabledboolean

по умолчанию: true

Временно выключить протяжку, не снимая хук.

Обе оси

С axis: "both" полотно тянется в любую сторону — так листается диаграмма Ганта:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

Как это работает

Здесь много неочевидного, и почти каждое решение — ответ на конкретную поломку:

  • Клик не ломается. Протяжка включается только после порога сдвига в 5 px, а «пойманный» ею клик гасится на фазе захвата. Без этого дрожание руки при клике по карточке открывало бы её вместо прокрутки — или наоборот.
  • Нативный drag&drop важнее. На dragstart протяжка отменяется, а лента у края начинает крутиться сама. Иначе карточку было бы нечем донести до колонки за пределами экрана: во время переноса указатель принадлежит браузеру.
  • Тач не трогаем. Там прокрутка пальцем и так родная, и перехват только испортил бы её.
  • Курсор и края — по факту. «Рука» и растворение краёв появляются, только когда ленте действительно есть куда ехать.
  • Инерция гаснет о край. Упёрлись — скорость обнуляется, лента не «дрожит» у границы. Долгий кадр (вкладка была в фоне) не телепортирует ленту: шаг ограничен 50 мс.

Стили

Визуальная часть живёт в @toimetdev/pathlogs-tokens/styles/scroll.css и цепляется за data-атрибуты, которые ставит хук:

html
<div data-pl-drag-scroll="true"      <!-- есть куда прокручивать: курсор «рука» -->
     data-pl-scroll-edge="both"     <!-- контент спрятан слева и справа -->
     data-pl-scroll-edge-y="end"    <!-- и снизу -->
     data-pl-scroll-keys="true">    <!-- слушает клавиши -->

Математика отдельно

Вся арифметика вынесена из хука и не знает про DOM — поэтому она покрыта тестами, а не проверяется руками:

tsx
import {
  isDragIntent,     // сдвиг превысил порог — это протяжка, а не клик
  flingVelocity,    // скорость броска по треку указателя
  decayVelocity,    // затухание инерции за кадр
  edgeScrollSpeed,  // скорость автопрокрутки у края при drag&drop
  hiddenEdges,      // какой край «продолжается» за экран
  keyboardScroll,   // что сделать по нажатой клавише
} from "@toimetdev/pathlogs-hooks";

Там же лежит attachDragScroll(el, getOptions) — привязка к элементу без React, если поведение нужно вне React-дерева.