Я реализовал response-dnd , гибкий миксин перетаскивания HTML5 для React с полным контролем DOM.
Существующие библиотеки перетаскивания не подходили для моего варианта использования, поэтому я написал свою собственную. Он похож на код, который мы запускаем около года на Stampsy.com, но переписан, чтобы воспользоваться преимуществами React и Flux.
Основные требования, которые у меня были:
- Создавать нулевой DOM или собственный CSS, оставляя это потребляющим компонентам;
- Как можно меньше структурировать потребляющие компоненты;
- Используйте перетаскивание HTML5 в качестве основного бэкэнда, но сделайте возможным добавление других бэкэндов в будущем;
- Как и в оригинальном API HTML5, упор делается на перетаскивание данных, а не только на «перетаскиваемые представления»;
- Скрыть причуды HTML5 API от потребляющего кода;
- Различные компоненты могут быть «источниками перетаскивания» или «целями перетаскивания» для разных типов данных;
- Разрешить одному компоненту содержать несколько источников перетаскивания и целей перетаскивания при необходимости;
- Упростите изменение внешнего вида объектов перетаскивания при перетаскивании или зависании совместимых данных;
- Упростите использование изображений для перетаскивания эскизов вместо скриншотов элементов, избегая причуд браузера.
Если они вам знакомы, читайте дальше.
Применение
Простой источник перетаскивания
Сначала объявите типы данных, которые можно перетаскивать.
Они используются для проверки «совместимости» источников и целей перетаскивания:
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Если у вас нет нескольких типов данных, эта библиотека может не для вас.)
Затем давайте создадим очень простой перетаскиваемый компонент, который при перетаскивании представляет IMAGE
:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var Image = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
dragSource: {
beginDrag() {
return {
item: this.props.image
};
}
}
});
},
render() {
return (
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
);
}
);
Указывая configureDragDrop
, мы сообщаем DragDropMixin
поведение этого компонента при перетаскивании. И перетаскиваемые, и перетаскиваемые компоненты используют один и тот же миксин.
Внутри configureDragDrop
нам нужно вызвать registerType
каждый из наших кастомов, ItemTypes
которые поддерживает компонент. Например, может быть несколько представлений изображений в приложении, и каждый из них предоставляет Бы dragSource
для ItemTypes.IMAGE
.
A dragSource
- это просто объект, определяющий, как работает источник перетаскивания. Вы должны реализовать beginDrag
для возврата элемент, который представляет данные, которые вы перетаскиваете, и, при необходимости, несколько параметров, которые регулируют пользовательский интерфейс перетаскивания. Вы можете дополнительно реализовать canDrag
запрет на перетаскивание или endDrag(didDrop)
выполнить некоторую логику, когда перетаскивание произошло (или не произошло). И вы можете поделиться этой логикой между компонентами, разрешив dragSource
для них генерировать общий миксин .
Наконец, вы должны использовать {...this.dragSourceFor(itemType)}
некоторые (один или несколько) элементов render
для присоединения обработчиков перетаскивания. Это означает, что у вас может быть несколько «маркеров перетаскивания» в одном элементе, и они могут даже соответствовать разным типам элементов. (Если вы не знакомы с синтаксисом JSX Spread Attributes , ознакомьтесь с ним).
Простая цель падения
Допустим, мы хотим ImageBlock
быть целью для IMAGE
s. Это почти то же самое, за исключением того, что мы должны дать registerType
в dropTarget
реализации:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
dropTarget: {
acceptDrop(image) {
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{this.props.image &&
<img src={this.props.image.url} />
}
</div>
);
}
);
Перетащить источник + перетащить цель в один компонент
Скажем, теперь мы хотим, чтобы пользователь мог вытащить изображение из ImageBlock
. Нам просто нужно добавить dragSource
к нему соответствующие и несколько обработчиков:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
dragSource: {
canDrag() {
return !!this.props.image;
},
beginDrag() {
return {
item: this.props.image
};
}
}
dropTarget: {
acceptDrop(image) {
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{/* Add {...this.dragSourceFor} handlers to a nested node */}
{this.props.image &&
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
}
</div>
);
}
);
Что еще возможно?
Я не рассмотрел все, но можно использовать этот API еще несколькими способами:
- Используйте
getDragState(type)
и, getDropState(type)
чтобы узнать, активно ли перетаскивание, и используйте его для переключения классов или атрибутов CSS;
- Укажите
dragPreview
, что Image
использовать изображения в качестве сопротивления заполнителей (использование , ImagePreloaderMixin
чтобы загрузить их);
- Допустим, мы хотим сделать
ImageBlocks
повторный заказ. Нам только нужно их реализовать dropTarget
и dragSource
за ItemTypes.BLOCK
.
- Предположим, мы добавляем другие типы блоков. Мы можем повторно использовать их логику переупорядочения, поместив ее в миксин.
dropTargetFor(...types)
позволяет указать несколько типов одновременно, поэтому в одной зоне перетаскивания можно поймать много разных типов.
- Когда вам нужен более детальный контроль, большинству методов передается событие перетаскивания, вызвавшее их, в качестве последнего параметра.
Актуальную документацию и инструкции по установке можно найти в репозитории response-dnd на Github .