Implementé react-dnd , un mixin flexible de arrastrar y soltar HTML5 para React con control DOM completo.
Las bibliotecas de arrastrar y soltar existentes no se ajustaban a mi caso de uso, así que escribí la mía propia. Es similar al código que hemos estado ejecutando durante aproximadamente un año en Stampsy.com, pero reescrito para aprovechar React y Flux.
Requisitos clave que tenía:
- Emite cero DOM o CSS propio, dejándolo en manos de los componentes consumidores;
- Imponer la menor estructura posible a los componentes consumidores;
- Use HTML5 arrastrar y soltar como backend principal pero haga posible agregar diferentes backends en el futuro;
- Al igual que la API HTML5 original, enfatice el arrastre de datos y no solo las "vistas que se pueden arrastrar";
- Ocultar las peculiaridades de la API HTML5 del código consumidor;
- Los diferentes componentes pueden ser "fuentes de arrastre" o "destinos de colocación" para diferentes tipos de datos;
- Permitir que un componente contenga varias fuentes de arrastre y soltar destinos cuando sea necesario;
- Facilite que los destinos para soltar cambien su apariencia si se arrastran o se desplazan datos compatibles
- Facilite el uso de imágenes para arrastrar miniaturas en lugar de capturas de pantalla de elementos, evitando las peculiaridades del navegador.
Si te suenan familiares, sigue leyendo.
Uso
Fuente de arrastre simple
Primero, declare los tipos de datos que se pueden arrastrar.
Se utilizan para comprobar la "compatibilidad" de las fuentes de arrastre y los destinos de colocación:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Si no tiene varios tipos de datos, es posible que esta biblioteca no sea para usted).
Luego, hagamos un componente arrastrable muy simple que, cuando se arrastra, representa IMAGE
:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var Image = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
// Specify all supported types by calling registerType(type, { dragSource?, dropTarget? })
registerType(ItemTypes.IMAGE, {
// dragSource, when specified, is { beginDrag(), canDrag()?, endDrag(didDrop)? }
dragSource: {
// beginDrag should return { item, dragOrigin?, dragPreview?, dragEffect? }
beginDrag() {
return {
item: this.props.image
};
}
}
});
},
render() {
// {...this.dragSourceFor(ItemTypes.IMAGE)} will expand into
// { draggable: true, onDragStart: (handled by mixin), onDragEnd: (handled by mixin) }.
return (
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
);
}
);
Al especificar configureDragDrop
, contamos DragDropMixin
el comportamiento de arrastrar y soltar de este componente. Tanto los componentes que se pueden arrastrar como los que se pueden soltar utilizan la misma mezcla.
En el interior configureDragDrop
, necesitamos llamar registerType
para cada uno de nuestros ItemTypes
componentes personalizados que admite. Por ejemplo, puede haber varias representaciones de imágenes en su aplicación, y cada proporcionaría una dragSource
deItemTypes.IMAGE
.
A dragSource
es solo un objeto que especifica cómo funciona la fuente de arrastre. Debe implementar beginDrag
para devolver el elemento que representa los datos que está arrastrando y, opcionalmente, algunas opciones que ajustan la IU de arrastre. Opcionalmente, puede implementar canDrag
para prohibir el arrastre, o endDrag(didDrop)
ejecutar alguna lógica cuando la caída ha ocurrido (o no). Y puede compartir esta lógica entre componentes dejando que se genere un mixin compartido dragSource
para ellos.
Finalmente, debe usar {...this.dragSourceFor(itemType)}
en algunos (uno o más) elementos render
para adjuntar controladores de arrastre. Esto significa que puede tener varios "controles de arrastre" en un elemento, e incluso pueden corresponder a diferentes tipos de elementos. (Si no está familiarizado con los atributos de difusión de JSX sintaxis de , compruébelo).
Destino de caída simple
Digamos que queremos ImageBlock
ser un objetivo de caída para IMAGE
s. Es más o menos lo mismo, excepto que necesitamos dar registerType
una dropTarget
implementación:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// dropTarget, when specified, is { acceptDrop(item)?, enter(item)?, over(item)?, leave(item)? }
dropTarget: {
acceptDrop(image) {
// Do something with image! for example,
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
// {...this.dropTargetFor(ItemTypes.IMAGE)} will expand into
// { onDragEnter: (handled by mixin), onDragOver: (handled by mixin), onDragLeave: (handled by mixin), onDrop: (handled by mixin) }.
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{this.props.image &&
<img src={this.props.image.url} />
}
</div>
);
}
);
Arrastrar origen + soltar destino en un componente
Supongamos que ahora queremos que el usuario pueda arrastrar una imagen fuera de ImageBlock
. Solo necesitamos agregarle lo apropiado dragSource
y algunos controladores:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// Add a drag source that only works when ImageBlock has an 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>
);
}
);
¿Qué más es posible?
No he cubierto todo, pero es posible usar esta API de varias maneras más:
- Utilice
getDragState(type)
ygetDropState(type)
para saber si el arrastre está activo y úselo para alternar clases o atributos de CSS;
- Especifique si
dragPreview
desea Image
usar imágenes como marcadores de posición de arrastre (use ImagePreloaderMixin
para cargarlas);
- Digamos, queremos hacer
ImageBlocks
reordenables. Solo los necesitamos para implementar dropTarget
y dragSource
para ItemTypes.BLOCK
.
- Supongamos que agregamos otros tipos de bloques. Podemos reutilizar su lógica de reordenamiento colocándola en un mixin.
dropTargetFor(...types)
permite especificar varios tipos a la vez, por lo que una zona de caída puede capturar muchos tipos diferentes.
- Cuando necesita un control más detallado, a la mayoría de los métodos se les pasa el evento de arrastre que los causó como último parámetro.
Para obtener documentación e instrucciones de instalación actualizadas, diríjase a react-dnd repo en Github .