События drag: start, drag, end

Механизм перетаскивания в D3.js реализуется через модуль d3.drag(). Он предоставляет универсальную систему обработки перемещения элементов мышью, тач-событиями и стилусом. Во время взаимодействия генерируются три основных события:

  • start — начало перетаскивания;
  • drag — процесс перемещения;
  • end — завершение перетаскивания.

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


Подключение drag-поведения

Базовая структура подключения:

const dragBehavior = d3.drag()
    .on("start", dragStarted)
    .on("drag", dragged)
    .on("end", dragEnded);

d3.selectAll("circle")
    .call(dragBehavior);

В этом примере:

  • создаётся объект поведения drag;
  • к нему подключаются обработчики событий;
  • поведение применяется к SVG-элементам.

Событие start

Событие start вызывается один раз в момент начала перетаскивания.

Основные задачи start

На практике внутри start обычно выполняются:

  • изменение внешнего вида элемента;
  • сохранение начальных координат;
  • подготовка состояния приложения;
  • отключение анимаций;
  • повышение элемента над другими.

Пример

function dragStarted(event, d) {
    d3.select(this)
        .attr("stroke", "black")
        .attr("stroke-width", 3);
}

После начала перетаскивания элемент получает чёрную обводку.


Объект event в start

В обработчик автоматически передаётся объект event.

Наиболее важные свойства:

Свойство Назначение
event.x текущая X-координата
event.y текущая Y-координата
event.dx изменение по X
event.dy изменение по Y
event.subject объект перетаскивания
event.active количество активных drag-операций

Координаты event.x и event.y

Координаты вычисляются относительно контейнера перетаскивания.

Пример:

function dragStarted(event) {
    console.log(event.x);
    console.log(event.y);
}

Если элемент расположен внутри SVG, координаты будут соответствовать SVG-системе координат.


Событие drag

Событие drag вызывается непрерывно во время перемещения указателя.

Именно здесь обычно изменяется положение элемента.


Перемещение SVG-элемента

Пример для circle

function dragged(event, d) {
    d3.select(this)
        .attr("cx", event.x)
        .attr("cy", event.y);
}

Во время движения координаты круга обновляются.


Полный пример

<svg width="600" height="400"></svg>
const svg = d3.select("svg");

svg.append("circle")
    .attr("cx", 100)
    .attr("cy", 100)
    .attr("r", 40)
    .attr("fill", "steelblue")
    .call(
        d3.drag()
            .on("start", dragStarted)
            .on("drag", dragged)
            .on("end", dragEnded)
    );

function dragStarted(event) {
    d3.select(this)
        .attr("fill", "orange");
}

function dragged(event) {
    d3.select(this)
        .attr("cx", event.x)
        .attr("cy", event.y);
}

function dragEnded(event) {
    d3.select(this)
        .attr("fill", "steelblue");
}

Событие end

Событие end вызывается после завершения перетаскивания.

Частые задачи

Внутри end часто выполняются:

  • возврат стилей;
  • сохранение новых координат;
  • запуск анимации;
  • отправка данных на сервер;
  • проверка попадания в область.

Пример завершения drag

function dragEnded(event, d) {
    console.log("Перетаскивание завершено");
}

Изменение z-index через raise()

Во время перетаскивания элемент может перекрываться другими объектами.

Метод raise() перемещает его наверх.

function dragStarted(event) {
    d3.select(this)
        .raise()
        .attr("stroke", "red");
}

Работа с данными

D3.js тесно связывает DOM и данные.

Во время drag можно изменять сами данные объекта.

Пример

function dragged(event, d) {
    d.x = event.x;
    d.y = event.y;

    d3.select(this)
        .attr("cx", d.x)
        .attr("cy", d.y);
}

Теперь координаты сохраняются не только визуально, но и внутри массива данных.


Перетаскивание группы элементов

Drag можно применять к контейнеру <g>.

Пример

svg.append("g")
    .call(
        d3.drag()
            .on("drag", function(event) {
                d3.select(this)
                    .attr(
                        "transform",
                        `translate(${event.x}, ${event.y})`
                    );
            })
    );

Использование transform вместо координат

Для сложных объектов удобнее применять transform.

Пример

function dragged(event) {
    d3.select(this)
        .attr(
            "transform",
            `translate(${event.x}, ${event.y})`
        );
}

Такой подход особенно полезен для:

  • групп элементов;
  • диаграмм;
  • сложных SVG-структур.

event.subject

event.subject содержит объект, который участвует в drag-операции.

Пример

function dragStarted(event) {
    console.log(event.subject);
}

Настройка drag.subject()

Можно вручную определить объект перетаскивания.

Пример

const drag = d3.drag()
    .subject(function(event, d) {
        return d;
    });

Ограничение области перемещения

Часто требуется запретить выход за пределы контейнера.

Пример

function dragged(event) {

    let x = Math.max(0, Math.min(600, event.x));
    let y = Math.max(0, Math.min(400, event.y));

    d3.select(this)
        .attr("cx", x)
        .attr("cy", y);
}

Drag и force simulation

Одно из наиболее важных применений drag — взаимодействие с forceSimulation.


Перетаскивание узлов графа

const drag = d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended);

Пример с force graph

function dragstarted(event, d) {

    if (!event.active) {
        simulation.alphaTarget(0.3).restart();
    }

    d.fx = d.x;
    d.fy = d.y;
}

Обновление позиции узла

function dragged(event, d) {
    d.fx = event.x;
    d.fy = event.y;
}

Освобождение узла

function dragended(event, d) {

    if (!event.active) {
        simulation.alphaTarget(0);
    }

    d.fx = null;
    d.fy = null;
}

event.active

Свойство показывает количество активных drag-операций.

Пример

console.log(event.active);

Это особенно важно при:

  • множественных касаниях;
  • одновременных взаимодействиях;
  • force simulation.

Работа с touch-событиями

d3.drag() автоматически поддерживает:

  • мышь;
  • touch;
  • stylus.

Дополнительная обработка обычно не требуется.


Фильтрация событий

Метод filter() позволяет ограничить запуск drag.

Пример

const drag = d3.drag()
    .filter(event => event.button === 0);

Разрешается только левая кнопка мыши.


Настройка контейнера

Метод container() задаёт систему координат.

Пример

const drag = d3.drag()
    .container(function() {
        return this.parentNode;
    });

Отключение стандартного поведения браузера

Иногда браузер начинает выделять текст или перетаскивать изображения.

Решение

svg {
    user-select: none;
}

Drag и Canvas

D3.js может работать не только с SVG, но и с Canvas.

В этом случае координаты обновляются внутри структуры данных, после чего сцена перерисовывается.

Пример

function dragged(event, d) {
    d.x = event.x;
    d.y = event.y;

    render();
}

Производительность drag

При большом количестве элементов важно избегать тяжёлых операций внутри drag.

Нежелательные действия

  • сложные вычисления;
  • повторные выборки DOM;
  • частые анимации;
  • создание новых объектов.

Оптимизация

Плохой вариант

function dragged(event) {
    d3.selectAll("circle")
        .attr("opacity", 0.5);
}

Более эффективный вариант

function dragged(event) {
    d3.select(this)
        .attr("cx", event.x);
}

Комбинирование drag и zoom

drag можно совмещать с zoom.

Пример

svg.call(d3.zoom());
node.call(d3.drag());

Однако необходимо учитывать конфликт событий.


Предотвращение конфликтов drag и zoom

Часто drag назначается только узлам, а zoom — контейнеру.

svg.call(zoom);

nodes.call(drag);

Такое разделение предотвращает взаимное блокирование событий.


Удаление обработчиков

Drag-события можно отключить.

Пример

selection.on(".drag", null);

Простая схема жизненного цикла

mousedown / touchstart
        ↓
start
        ↓
drag
        ↓
drag
        ↓
drag
        ↓
end

Порядок вызова событий

  1. Пользователь нажимает кнопку мыши.
  2. Срабатывает start.
  3. Во время движения вызывается drag.
  4. После отпускания кнопки вызывается end.

Использование стрелочных функций

Стрелочные функции не имеют собственного this.

Ошибка

.on("drag", (event) => {
    d3.select(this);
})

Здесь this не будет указывать на DOM-элемент.


Правильный вариант

.on("drag", function(event) {
    d3.select(this);
})

Использование datum внутри drag

function dragged(event, d) {
    console.log(d);
}

Параметр d содержит привязанные данные элемента.


Проверка источника события

Иногда требуется узнать исходное событие браузера.

Пример

console.log(event.sourceEvent);

Проверка типа события

console.log(event.sourceEvent.type);

Результат:

mousemove
touchmove
pointermove

Drag нескольких объектов

svg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .call(d3.drag()
        .on("drag", dragged));

Каждый объект получает собственное drag-поведение.


Перемещение HTML-элементов

Drag работает не только с SVG.

Пример

d3.select(".box")
    .call(
        d3.drag()
            .on("drag", function(event) {

                d3.select(this)
                    .style("left", event.x + "px")
                    .style("top", event.y + "px");
            })
    );

CSS для HTML drag

.box {
    position: absolute;
}

Использование classed()

Во время drag удобно менять CSS-классы.

Пример

function dragStarted(event) {
    d3.select(this)
        .classed("active", true);
}

function dragEnded(event) {
    d3.select(this)
        .classed("active", false);
}

Пример активного состояния

.active {
    stroke: red;
    stroke-width: 4px;
}

Практические области применения

События start, drag и end активно используются в:

  • интерактивных графах;
  • редакторах диаграмм;
  • drag-and-drop интерфейсах;
  • визуализациях данных;
  • сетевых графах;
  • конструкторах UI;
  • Canvas-приложениях;
  • игровых интерфейсах;
  • системах аналитики;
  • SVG-редакторах.

Ключевые особенности drag-системы D3.js

  • единый API для мыши и touch;
  • тесная интеграция с данными;
  • работа с SVG и Canvas;
  • гибкая настройка поведения;
  • высокая производительность;
  • поддержка сложных интерактивных сценариев;
  • совместимость с force simulation и zoom.