Ich habe react-dnd implementiert , ein flexibles HTML5-Drag-and-Drop-Mixin für React mit vollständiger DOM-Steuerung.
Bestehende Drag-and-Drop-Bibliotheken passten nicht zu meinem Anwendungsfall, daher schrieb ich meine eigenen. Es ähnelt dem Code, den wir seit ungefähr einem Jahr auf Stampsy.com ausführen, wurde jedoch neu geschrieben, um die Vorteile von React und Flux zu nutzen.
Hauptanforderungen, die ich hatte:
- Geben Sie kein eigenes DOM oder CSS aus und überlassen Sie es den verbrauchenden Komponenten.
- Den verbrauchenden Komponenten so wenig Struktur wie möglich auferlegen;
- Verwenden Sie HTML5-Drag & Drop als primäres Backend, aber ermöglichen Sie es, in Zukunft verschiedene Backends hinzuzufügen.
- Betonen Sie wie bei der ursprünglichen HTML5-API das Ziehen von Daten und nicht nur das Ziehen von Ansichten.
- Verstecken Sie HTML5-API-Macken vor dem konsumierenden Code.
- Verschiedene Komponenten können "Drag-Quellen" oder "Drop-Ziele" für verschiedene Arten von Daten sein.
- Erlauben Sie einer Komponente, bei Bedarf mehrere Drag-Quellen und Drop-Ziele zu enthalten.
- Machen Sie es Drop-Zielen einfach, ihr Erscheinungsbild zu ändern, wenn kompatible Daten gezogen oder mit dem Mauszeiger darüber bewegt werden.
- Machen Sie es sich einfach, Bilder zum Ziehen von Miniaturansichten anstelle von Element-Screenshots zu verwenden, um Browser-Macken zu umgehen.
Wenn Ihnen diese bekannt vorkommen, lesen Sie weiter.
Verwendung
Einfache Drag Source
Deklarieren Sie zunächst Datentypen, die gezogen werden können.
Diese werden verwendet, um die „Kompatibilität“ von Drag-Quellen und Drop-Zielen zu überprüfen:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Wenn Sie nicht über mehrere Datentypen verfügen, ist diese Bibliothek möglicherweise nicht für Sie geeignet.)
Dann erstellen wir eine sehr einfache ziehbare Komponente, die beim Ziehen Folgendes darstellt 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)} />
);
}
);
Durch Angabe erklären configureDragDropwir DragDropMixindas Drag-Drop-Verhalten dieser Komponente. Sowohl ziehbare als auch ablegbare Komponenten verwenden das gleiche Mixin.
Im Inneren configureDragDropmüssen wir registerTypejede unserer benutzerdefinierten ItemTypesKomponenten aufrufen , die von der Komponente unterstützt werden. Beispielsweise enthält Ihre App möglicherweise mehrere Darstellungen von Bildern, von denen jede ein dragSourcefür bereitstellt ItemTypes.IMAGE.
A dragSourceist nur ein Objekt, das angibt, wie die Drag-Quelle funktioniert. Sie müssen implementieren beginDrag, um ein Element zurückzugeben, das die Daten darstellt, die Sie ziehen, und optional einige Optionen, die die Benutzeroberfläche zum Ziehen anpassen. Sie können optional implementieren, canDragum das Ziehen zu verbieten oder endDrag(didDrop)eine Logik auszuführen, wenn der Abwurf aufgetreten ist (oder nicht). Und Sie können diese Logik zwischen Komponenten teilen, indem Sie ein gemeinsames Mixin dragSourcefür sie generieren lassen.
Schließlich müssen Sie {...this.dragSourceFor(itemType)}einige (ein oder mehrere) Elemente verwenden render, um Drag-Handler anzuhängen. Dies bedeutet, dass Sie mehrere „Ziehpunkte“ in einem Element haben können und diese sogar verschiedenen Elementtypen entsprechen können. (Wenn Sie mit der Syntax von JSX Spread Attributes nicht vertraut sind, lesen Sie sie durch.)
Einfaches Drop-Ziel
Nehmen wir ImageBlockan , wir wollen ein Drop-Ziel für IMAGEs sein. Es ist ziemlich ähnlich, außer dass wir registerTypeeine dropTargetImplementierung geben müssen:
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>
);
}
);
Quelle ziehen + Ziel in einer Komponente ablegen
Angenommen, wir möchten jetzt, dass der Benutzer ein Bild herausziehen kann ImageBlock. Wir müssen nur dragSourceein paar entsprechende Handler hinzufügen :
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>
);
}
);
Was ist sonst noch möglich?
Ich habe nicht alles behandelt, aber es ist möglich, diese API auf einige weitere Arten zu verwenden:
- Verwenden Sie
getDragState(type)und getDropState(type), um zu erfahren, ob das Ziehen aktiv ist, und um CSS-Klassen oder -Attribute umzuschalten.
- Geben Sie
dragPreviewan Image, dass Bilder als Drag-Platzhalter verwendet werden sollen ( ImagePreloaderMixinzum Laden).
- Sagen wir, wir wollen
ImageBlocksnachbestellbar machen . Wir brauchen sie nur zu implementieren dropTargetund dragSourcefür ItemTypes.BLOCK.
- Angenommen, wir fügen andere Arten von Blöcken hinzu. Wir können ihre Neuordnungslogik wiederverwenden, indem wir sie in ein Mixin einfügen.
dropTargetFor(...types) Ermöglicht die gleichzeitige Angabe mehrerer Typen, sodass eine Drop-Zone viele verschiedene Typen erfassen kann.
- Wenn Sie eine feinkörnigere Steuerung benötigen, wird den meisten Methoden ein Drag-Ereignis übergeben, das sie als letzten Parameter verursacht hat.
Aktuelle Dokumentationen und Installationsanweisungen finden Sie unter Reagieren und Repo auf Github .