Я реализовал 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быть целью для IMAGEs. Это почти то же самое, за исключением того, что мы должны дать 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 .