Механизм drag and drop в Deck.gl основан на событийной модели уровня
DeckGL и системе picking, которая связывает экранные
координаты с объектами данных внутри слоёв. Перетаскивание объектов
реализуется не как отдельная встроенная сущность слоя, а как комбинация
обработчиков событий, обновления состояния и переинтерпретации данных
слоя.
Ключевая идея заключается в том, что интерактивность в Deck.gl не изменяет геометрию напрямую — изменяются данные, на основе которых слой заново рендерится.
Deck.gl предоставляет набор событий, которые формируют полный цикл drag and drop:
onDragStartonDragonDragEndonHoveronClickЭти события приходят из контроллера DeckGL и
обрабатываются на уровне сцены. Каждое событие содержит:
x, y — координаты курсора в пикселяхcoordinate — географические координаты (если
используется карта)object — объект данных, попавший под курсор (через
picking)layer — слой, из которого был получен объектpicked — флаг попадания в объектСобытия drag формируют непрерывный поток обновлений, где состояние объекта пересчитывается на каждом кадре перемещения мыши.
Механизм picking выполняет преобразование:
экранные координаты → объект слоя → данные
Каждый слой может быть “pickable”, если установлен параметр:
pickable: true
При этом Deck.gl автоматически строит picking buffer и сопоставляет пиксели с объектами данных.
В контексте drag and drop важны следующие свойства результата picking:
index — индекс элемента в массиве данных слояobject — исходный объект данныхcoordinate — рассчитанная позиция на картеsourceLayer — слой-источникТипичный цикл drag операции выглядит следующим образом:
onDragStart
onDrag
onDragEnd
Важно, что Deck.gl не хранит состояние перетаскивания внутри слоя — оно реализуется внешним состоянием приложения.
Для слоёв типа ScatterplotLayer типичный сценарий
включает обновление массива данных:
const [data, setData] = useState(initialData);
const [draggedIndex, setDraggedIndex] = useState(null);
const [isDragging, setIsDragging] = useState(false);
Инициация слоя:
new ScatterplotLayer({
id: 'points',
data,
pickable: true,
getPosition: d => d.coordinates,
getRadius: 100,
onDragStart: info => {
if (info.object) {
setDraggedIndex(info.index);
setIsDragging(true);
}
},
onDrag: info => {
if (!isDragging || draggedIndex === null) return;
const newData = [...data];
newData[draggedIndex] = {
...newData[draggedIndex],
coordinates: info.coordinate
};
setData(newData);
},
onDragEnd: () => {
setIsDragging(false);
setDraggedIndex(null);
}
});
Ключевой принцип: изменение данных вызывает перерасчёт геометрии слоя, что визуально воспринимается как перемещение объекта.
В зависимости от типа слоя используются разные системы координат:
lng, lat)x, y)Для картографических слоёв Deck.gl автоматически выполняет преобразование через контекст view state.
Однако при кастомной логике часто требуется явное использование:
info.coordinate — итоговая географическая позицияdeck.project / deck.unproject — ручные
преобразованияОсобенно важно учитывать, что drag происходит в экранном пространстве, а обновление данных — в географическом.
При использовании Deck.gl поверх Mapbox взаимодействие происходит через синхронизацию состояния вида:
<DeckGL
initialViewState={viewState}
controller={true}
layers={layers}
/>
События drag влияют не только на объект, но и могут изменять:
longitude, latitude)Если drag используется для перемещения объектов, необходимо отключать влияние на камеру:
controller={{ dragPan: false }}
В Deck.gl существует два независимых типа drag:
Drag камеры
ControllerDrag объектов
onDrag*Конфликт между ними возникает, когда оба режима активны одновременно. Для корректной работы вводится приоритет:
Сложные сцены требуют централизованного состояния:
draggedObjectsourceLayerIdinitialCoordinateoffsetПример вычисления смещения:
const offset = {
lng: current.coordinate[0] - start.coordinate[0],
lat: current.coordinate[1] - start.coordinate[1]
};
Это позволяет сохранять стабильность движения даже при высоком зуме.
В IconLayer перетаскивание часто связано с якорными
точками:
new IconLayer({
id: 'icons',
data,
pickable: true,
getPosition: d => d.position,
getIcon: d => d.icon,
sizeScale: 1,
onDrag: info => {
if (!info.object) return;
setData(prev =>
prev.map((d, i) =>
i === info.index
? { ...d, position: info.coordinate }
: d
)
);
}
});
Здесь важно учитывать anchor:
icon-anchorgetSizeПеретаскивание в Deck.gl может становиться узким местом при больших массивах данных. Основные оптимизации:
Immutable структурПри больших наборах данных предпочтительно:
updateTriggersnew ScatterplotLayer({
id: 'points',
data,
pickable: true,
getPosition: d => d.coordinates,
updateTriggers: {
getPosition: draggedIndex
}
});
Это позволяет ограничить перерасчёт только затронутыми элементами.
Для сложных сценариев используется расширение слоя:
stateproject внутри Layerlayer.stateПодход применяется при:
Часто требуется ограничить drag:
const clampLngLat = ([lng, lat]) => [
Math.max(-180, Math.min(180, lng)),
Math.max(-85, Math.min(85, lat))
];
Или в пределах bounding box:
if (lng < minLng || lng > maxLng) return;
Такая логика внедряется в обработчик onDrag.
При плавных перемещениях часто применяется интерполяция:
Deck.gl не навязывает модель движения, поэтому обновление состояния может сопровождаться requestAnimationFrame для сглаживания траектории.
При наложении слоёв важен порядок:
getCursor может сигнализировать активный dragvisible влияет на участие в pickingДля сложных сцен используется фильтрация:
pickable: d => d.interactive
При необходимости синхронного перемещения объектов разных слоёв применяется shared state:
Такой подход используется при: