Unter den vielen JavaScript-APIs, die in HTML5 hinzugefügt wurden, befand sich auch Drag and Drop (im Folgenden als DnD bezeichnet), das native DnD-Unterstützung in den Browser brachte und es Entwicklern erleichterte, diese interaktive Funktion in Anwendungen zu implementieren. Das Erstaunliche, was passiert, wenn Funktionen einfacher zu implementieren sind, ist, dass die Leute anfangen, allerlei alberne, unpraktische Dinge damit zu machen, wie das, was wir heute machen: ein Parkspiel!
DnD benötigt nur wenige Dinge, um zu funktionieren
- Etwas zum Ziehen
- Irgendwo zum Ablegen
- JavaScript-Ereignishandler am Ziel, um dem Browser mitzuteilen, dass er ablegen kann
Wir beginnen mit der Erstellung unserer ziehbaren Elemente.
Ziehen
Sowohl <img>- als auch <a>-Elemente (mit gesetztem href-Attribut) sind standardmäßig ziehbar. Wenn Sie ein anderes Element ziehen möchten, müssen Sie das Attribut `draggable` auf true setzen.
Wir beginnen mit dem HTML, das die Bilder für unsere vier Fahrzeuge einrichtet: Feuerwehrauto, Krankenwagen, Auto und Fahrrad.
<ul class="vehicles">
<li>
<!-- Fire Truck -->
<!-- <code>img<code> elements don't need a <code>draggable<code> attribute like other elements -->
<img id="fire-truck" alt="fire truck" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Ftruck-clip-art-fire-truck4.png?1519011787956"/>
</li>
<li>
<!-- Ambulance -->
<img id="ambulance" alt="ambulance" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fambulance5.png?1519011787610">
</li>
<li>
<!-- Car -->
<img id="car" alt="car" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fcar-20clip-20art-1311497037_Vector_Clipart.png?1519011788408">
</li>
<li>
<!-- Bike -->
<img id="bike" alt="bicycle" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fbicycle-20clip-20art-bicycle3.png?1519011787816">
</li>
</ul>
Da Bilder standardmäßig ziehbar sind, sehen Sie beim Ziehen eines beliebigen Bildes ein Geisterbild.

Das bloße Hinzufügen eines `draggable`-Attributs zu einem Element, das kein Bild oder Link ist, ist in den meisten Browsern alles, was Sie brauchen, um ein Element ziehbar zu machen. Um Elemente in allen Browsern ziehbar zu machen, müssen Sie einige Ereignishandler definieren. Diese sind auch nützlich, um zusätzliche Funktionalität hinzuzufügen, wie z. B. einen Rahmen, wenn ein Element gezogen wird, oder einen Ton, wenn es nicht mehr gezogen wird. Dafür benötigen Sie einige Drag-Ereignishandler, also sehen wir uns diese einmal an.
Drag-Ereignisse
Es gibt drei Drag-bezogene Ereignisse, auf die Sie hören können, aber wir werden nur zwei verwenden: dragstart und dragend.
dragstart– Wird ausgelöst, sobald wir mit dem Ziehen beginnen. Hier können wir die Drag-Daten und den Drag-Effekt definieren.dragend– Wird ausgelöst, wenn ein ziebares Element abgelegt wird. Dieses Ereignis wird im Allgemeinen kurz nach dem `drop`-Ereignis der Dropzone ausgelöst.
Wir werden kurz darauf eingehen, was die Drag-Daten und der Drag-Effekt sind.
let dragged; // Keeps track of what's being dragged - we'll use this later!
function onDragStart(event) {
let target = event.target;
if (target && target.nodeName === 'IMG') { // If target is an image
dragged = target;
event.dataTransfer.setData('text', target.id);
event.dataTransfer.dropEffect = 'move';
// Make it half transparent when it's being dragged
event.target.style.opacity = .3;
}
}
function onDragEnd(event) {
if (event.target && event.target.nodeName === 'IMG') {
// Reset the transparency
event.target.style.opacity = ''; // Reset opacity when dragging ends
dragged = null;
}
}
// Adding event listeners
const vehicles = document.querySelector('.vehicles');
vehicles.addEventListener('dragstart', onDragStart);
vehicles.addEventListener('dragend', onDragEnd);
In diesem Code passieren ein paar Dinge
- Wir definieren die Drag-Daten. Jedes Drag-Ereignis hat eine Eigenschaft namens
dataTransfer, die die Daten des Ereignisses speichert. Sie können die MethodesetData(type, data)verwenden, um ein gezogenes Element zu den Drag-Daten hinzuzufügen. Wir speichern die ID des gezogenen Bildes als Typ'text'in Zeile 7. - Wir speichern das gezogene Element in einer globalen Variable. Ich weiß, ich weiß. Global ist gefährlich für den Geltungsbereich, aber hier ist, warum wir es tun: Obwohl Sie das gezogene Element mit
setDataspeichern können, können Sie es in allen Browsern (außer Firefox) nicht mitevent.dataTransfer.getData()abrufen, da die Drag-Daten im Schutzmodus sind. Mehr dazu können Sie hier lesen. Ich wollte die Definition der Drag-Daten erwähnen, nur damit Sie davon wissen. - Wir setzen
dropEffectaufmove. Die EigenschaftdropEffectwird verwendet, um das Feedback zu steuern, das dem Benutzer während einer Drag-and-Drop-Operation gegeben wird. Sie ändert zum Beispiel, welcher Cursor der Browser während des Ziehens anzeigt. Es gibt drei Effekte: `copy`, `move` und `link`.copy– Zeigt an, dass die gezogenen Daten von ihrer Quelle zum Ablageort kopiert werden.move– Zeigt an, dass die gezogenen Daten verschoben werden.link– Zeigt an, dass eine Art Beziehung zwischen Quelle und Ablageort hergestellt wird.
Jetzt haben wir ziehbare Fahrzeuge, aber nirgends, wo wir sie ablegen können
Siehe den Pen 1 – Can you park here? von Omayeli Arenyeka (@yelly) auf CodePen.
Ablegen
Standardmäßig können nur Formularelemente wie <input> ein ziebares Element als Drop akzeptieren. Wir werden unsere „Dropzone“ in ein <section>-Element packen, daher müssen wir Drop-Ereignishandler hinzufügen, damit es Drops akzeptieren kann, genau wie ein Formularelement.
Zuerst müssen wir ihm eine Breite, Höhe und Hintergrundfarbe geben, da es ein leeres Element ist, damit wir es auf dem Bildschirm sehen können.

Dies sind die Parameter, die uns für Drop-Ereignisse zur Verfügung stehen
dragenter– Wird in dem Moment ausgelöst, in dem ein ziebares Element einen ablegbaren Bereich betritt. Mindestens 50 % des ziehbaren Elements müssen sich innerhalb der Dropzone befinden.dragover– Dasselbe wiedragenter, aber es wird wiederholt aufgerufen, während sich das ziehbare Element innerhalb der Dropzone befindet.dragleave– Wird ausgelöst, sobald sich ein ziebares Element von einer Dropzone wegbewegt hat.drop– Wird ausgelöst, wenn das ziehbare Element losgelassen wurde und der Ablagebereich zustimmt, den Drop zu akzeptieren.
function onDragOver(event) {
// Prevent default to allow drop
event.preventDefault();
}
function onDragLeave(event) {
event.target.style.background = '';
}
function onDragEnter(event) {
const target = event.target;
if (target) {
event.preventDefault();
// Set the dropEffect to move
event.dataTransfer.dropEffect = 'move'
target.style.background = '#1f904e';
}
}
function onDrop(event) {
const target = event.target;
if ( target) {
target.style.backgroundColor = '';
event.preventDefault();
// Get the id of the target and add the moved element to the target's DOM
dragged.parentNode.removeChild(dragged);
dragged.style.opacity = '';
target.appendChild(dragged);
}
}
const dropZone = document.querySelector('.drop-zone');
dropZone.addEventListener('drop', onDrop);
dropZone.addEventListener('dragenter', onDragEnter);
dropZone.addEventListener('dragleave', onDragLeave);
dropZone.addEventListener('dragover', onDragOver);
Wenn Sie sich fragen, warum wir immer wieder event.preventDefault() aufrufen: Das liegt daran, dass der Browser standardmäßig davon ausgeht, dass jedes Ziel kein gültiges Ablageziel ist. Das ist nicht immer und für alle Browser wahr, aber sicher ist sicher! Durch den Aufruf von preventDefault() für die Ereignisse dragenter, dragover und `drop` wird dem Browser mitgeteilt, dass das aktuelle Ziel ein gültiges Ablageziel ist.
Jetzt haben wir eine einfache Drag-and-Drop-Anwendung!
Siehe den Pen 2 – Can you park here? von Omayeli Arenyeka (@yelly) auf CodePen.
Es macht Spaß, aber noch nicht ganz so frustrierend wie das Parken. Wir müssen einige Regeln aufstellen, um das zu erreichen.
Regeln und Validierung
Ich habe mir ein paar zufällige Parkregeln ausgedacht und ermutige Sie, eigene zu erstellen. Parkschilder haben normalerweise Tage und Zeiten, an denen Sie parken dürfen, sowie welche Fahrzeugtypen zu diesem Zeitpunkt parken dürfen. Als wir unsere ziehbaren Objekte erstellten, hatten wir vier Fahrzeuge: einen Krankenwagen, ein Feuerwehrauto, ein normales Auto und ein Fahrrad. Also werden wir Regeln für sie erstellen.
- Krankenwagenparken nur: Montag bis Freitag, 21:00 bis 03:00 Uhr.
- Feuerwehrparken nur: Den ganzen Tag am Wochenende.
- Parken für normale Autos: Montag bis Freitag, 03:00 bis 15:00 Uhr.
- Fahrradparken: Montag bis Freitag, 15:00 bis 21:00 Uhr.
Nun übersetzen wir diese Regeln in Code. Wir werden zwei Bibliotheken verwenden, um Zeit und Bereiche zu verwalten: Moment und Moment-range.
Die Skripte sind bereits in Codepen verfügbar, um sie zu jedem neuen Demo hinzuzufügen, aber wenn Sie außerhalb von Codepen entwickeln, können Sie sie von hier kopieren oder verlinken
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/3.1.1/moment-range.js"></script>
Dann erstellen wir ein Objekt, um alle Parkregeln zu speichern.
window['moment-range'].extendMoment(moment);
// The array of weekdays
const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
const parkingRules = {
ambulance: {
// The ambulance can only park on weekdays...
days: weekdays,
// ...from 9pm to 3am (the next day)
times: createRange(moment().set('hour', 21), moment().add(1, 'day').set('hour', 3))
},
'fire truck': {
// The fire truck can obnly park on Saturdays and Sundays, but all day
days: ['Saturday', 'Sunday']
},
car: {
// The car can only park on weekdays...
days: weekdays,
// ...from 3am - 3pm (the same day)
times: createRange(moment().set('hour', 3), moment().set('hour', 15))
},
bicycle: {
// The car can only park on weekdays...
days: weekdays,
// ...from 3pm - 9pm (the same day)
times: createRange(moment().set('hour', 15), moment().set('hour', 21))
}
};
function createRange(start, end) {
if (start && end) {
return moment.range(start, end);
}
}
Jedes Fahrzeug im parkingRules-Objekt hat eine days-Eigenschaft mit einem Array von Tagen, an denen es parken kann, und eine times-Eigenschaft, die einen Zeitbereich darstellt. Um die aktuelle Zeit mit Moment zu erhalten, rufen Sie moment() auf. Um einen Bereich mit Moment-range zu erstellen, übergeben Sie eine Start- und Endzeit an die Funktion moment.range.
Jetzt fügen wir in den zuvor definierten onDragEnter- und onDrop-Ereignishandlern einige Prüfungen hinzu, um sicherzustellen, dass ein Fahrzeug parken kann. Unser alt-Attribut im img-Tag speichert den Fahrzeugtyp, also übergeben wir diesen an eine canPark-Methode, die zurückgibt, ob das Auto geparkt werden kann. Wir haben auch visuelle Hinweise (Änderung des Hintergrunds) hinzugefügt, um dem Benutzer mitzuteilen, ob ein Fahrzeug geparkt werden kann oder nicht.
function onDragEnter(event) {
const target = event.target;
if (dragged && target) {
const vehicleType = dragged.alt; // e.g bicycle, ambulance
if (canPark(vehicleType)) {
event.preventDefault();
// Set the dropEffect to move
event.dataTransfer.dropEffect = 'move';
/* Change color to green to show it can be dropped /*
target.style.background = '#1f904e';
}
else {
/* Change color to red to show it can't be dropped. Notice we
* don't call event.preventDefault() here so the browser won't
* allow a drop by default
*/
target.style.backgroundColor = '#d51c00';
}
}
}
function onDrop(event) {
const target = event.target;
if (target) {
const data = event.dataTransfer.getData('text');
const dragged = document.getElementById(data);
const vehicleType = dragged.alt;
target.style.backgroundColor = '';
if (canPark(vehicleType)) {
event.preventDefault();
// Get the ID of the target and add the moved element to the target's DOM
dragged.style.opacity = '';
target.appendChild(dragged);
}
}
}
Dann erstellen wir die canPark-Methode.
function getDay() {
return moment().format('dddd'); // format as 'monday' not 1
}
function getHours() {
return moment().hour();
}
function canPark(vehicle) {
/* Check the time and the type of vehicle being dragged
* to see if it can park at this time
*/
if (vehicle && parkingRules[vehicle]) {
const rules = parkingRules[vehicle];
const validDays = rules.days;
const validTimes = rules.times;
const curDay = getDay();
if (validDays) {
/* If the current day is included on the parking days for the vehicle
* And if the current time is within the range
*/
return validDays.includes(curDay) && (validTimes ? validTimes.contains(moment()) : true);
/* Moment.range has a contains function that checks
* to see if your range contains a moment.
https://github.com/rotaready/moment-range#contains
*/
}
}
return false;
}
Jetzt können nur noch Autos parken, die zum Parken erlaubt sind. Zuletzt fügen wir die Regeln auf dem Bildschirm hinzu und gestalten sie.
Hier ist das Endergebnis
Siehe den Pen 3 – Can you park here? von Omayeli Arenyeka (@yelly) auf CodePen.
Es gibt viele Möglichkeiten, dies zu verbessern
- Generieren Sie die HTML-Liste der Regeln automatisch aus dem
parkingRules-Objekt! - Fügen Sie Soundeffekte hinzu!
- Fügen Sie die Möglichkeit hinzu, Fahrzeuge ohne Seitenaktualisierung zum ursprünglichen Punkt zurückzuziehen.
- All diese lästigen globalen Variablen.
Aber das überlasse ich Ihnen.
Wenn Sie mehr über die DnD-API und einige Kritiken dazu erfahren möchten, hier ist einige gute Lektüre
- WHATWG-Spezifikation
- Working with HTML5 Drag-and-Drop – Pro HTML5 Programming, Kapitel 9, von Jen Simmons
- Accessible Drag and Drop Using WAI-ARIA – Barrierefreiheitsüberlegungen von Dev.Opera
- Native HTML5 Drag and Drop – HTML5 Rocks Tutorial
- The HTML5 drag and drop disaster – QuirksMode-Beitrag mit hilfreichem Kontext zur Implementierung des DnD-Moduls
Das ist ein großartiges Tutorial, vielen Dank
Danke!
Hallo Yeli und andere,
In der Tat gut, ab und zu etwas zu lesen, das man noch nicht wusste. Und da Lesen ohne Experimentieren wie Schwimmen ohne Wasser ist, habe ich einige Möglichkeiten ausprobiert. Geradeaus (nur ein Fahrzeug auf dem Parkplatz ablegen): kein Problem.
Als ich versuchte, mehr Fahrzeuge in den Dropzone-Bereich zu bringen, wurde die Situation komplexer.
Unter Win7 auf dem PC stieß ich auf einige Browserunterschiede und (für mich) seltsame Phänomene in CodePen #2. Die Größen und Proportionen (proportional/nicht proportional) der abgelegten Bilder weichen ab. – Das Ablegen über einem bereits abgelegten Fahrzeug lässt eines davon verschwinden. – Und ein verschwundenes Fahrzeug (laut Entwicklertools: auch aus dem HTML-DOM entfernt) kann wiederhergestellt werden (!), indem das vorhandene Fahrzeug erneut an eine andere Stelle in der Dropzone gezogen und abgelegt wird. Der Spuk muss irgendwo im Skript liegen; da ich kein JavaScript-Held bin, sehe ich nicht wie.
Um das Beispiel zu verbessern, habe ich einige erweiterte Pens erstellt, in denen wiederholtes Ablegen erlaubt ist, Browserunterschiede neutralisiert werden und Autos und Fahrräder nicht mehr verschwinden. Natürlich gibt es viele andere CSS/JS-Möglichkeiten, um ähnliche Ergebnisse zu erzielen – der Spielplatz ist offen. :-)
Die vollständige Entdeckungsreise (mit einigen Bildschirmaufnahmen und Links zu den geforkten neuen Pens) kann hier gelesen werden: clba.nl/testing/parking
Grüße,
Francky
Hallo Francky, bisher konnte ich nur kurz reinschauen, aber vielen Dank dafür! Ich habe schon viel gelernt. Es ist sehr interessant, Unterschiede zwischen Browsern und Geräten herauszufinden. Ich werde mir bald alles durchsehen :)