In jeder App mit Benutzeravataren sollten Benutzer diese Avatare ändern können. Alles, was dies erleichtert, ist wünschenswert. Viele Apps beginnen mit dem Twitter-Avatar, Facebook-Avatar oder Gravatar eines Benutzers. Das ist ein kluger Schachzug. Avatare geben Benutzern das Gefühl der Eigenverantwortung für einen virtuellen Raum, sodass jede Möglichkeit, ihnen ihren gewünschten Avatar zu verschaffen, gut für das Engagement ist.
Erstellen wir eine Seite, auf der ein Benutzer seinen Avatar mit möglichst geringer Reibung aktualisieren kann: Er zieht einfach ein Bild irgendwo auf die Seite und das war's.

Das Arbeitstier von Drag and Drop
Der vielleicht wichtigste Teil, den wir behandeln werden, ist das Drop-Ereignis. Hier erhalten wir Zugriff auf die Datei und können damit tun, was wir tun müssen. Wir verwenden jQuery, um uns bei Ereignissen und Ähnlichem zu helfen.
// Required for drag and drop file access
jQuery.event.props.push('dataTransfer');
$("body").on('drop', function(event) {
// Or else the browser will open the file
event.preventDefault();
// Do something with the file(s)
var files = event.dataTransfer.files;
}
Interessanterweise funktioniert das oben Gesagte in dieser Form **nicht**. Ein weiterer Punkt ist erforderlich: das Verhindern des Standardverhaltens des dragover-Ereignisses. Das ist in Ordnung, da wir dieses Ereignis verwenden werden, um eine Art UI-Änderung vorzunehmen, um die "Drop-Fähigkeit" zu betonen.
$("body").on("dragover", function(event) {
// Do something to UI to make page look droppable
// Required for drop to work
return false;
});
Und natürlich diese UI-Änderung entfernen, wenn der Benutzer den Drop nicht durchführt
$("body").on("dragleave", function(event) {
// Remove UI change
});
Umgang mit der gedroppten Datei
Drag and Drop kann mehrere Dateien verarbeiten. Wir behandeln hier nur eine Datei, also nehmen wir einfach die erste.
$("body").on('drop', function(event) {
event.preventDefault();
var file = event.dataTransfer.files[0];
if (file.type.match('image.*')) {
// Deal with file
} else {
// However you want to handle error that dropped file wasn't an image
}
}
Vielleicht wären Sie wirklich nett, würden Sie über alle Dateien iterieren und das erste Bild finden, anstatt es basierend auf der ersten Datei abzulehnen.
Größenanpassung des Avatars
Es gibt serverseitige Möglichkeiten, Bilder in der Größe zu ändern, aber das erfordert einen Roundtrip (langsam) und die Übertragung potenziell riesiger Dateien (langsam). Wir können den Avatar auf die Größe ändern, die wir für unsere App benötigen, **direkt auf der Clientseite**. Das ist wahnsinnig schnell.
Sie können dies tun, indem Sie ein <canvas> erstellen, das Bild auf die Leinwand zeichnen und dann die Leinwand als Data-URI exportieren. Andrea Giammarchi hat ein exzellentes Skript dafür. Sie würden dieses Skript einfach vor all dem benutzerdefinierten Code einfügen, den wir schreiben.
Quadratische Avatare
In unserem Beispiel sind alle unsere Avatare Quadrate. Das Quadratisieren ist etwas knifflig. Erlauben Sie Rechtecke und zentrieren Sie sie einfach? Fügen Sie einen Leerraum um die Ränder hinzu, damit Rechtecke wirklich Quadrate sind? Beschneiden Sie das Bild, damit es ein Quadrat ist? Wenn Sie zuschneiden, von welchem ursprünglichen Punkt aus schneiden Sie? Schneiden Sie vor oder nach der Größenanpassung?
- Nehmen wir das Zuschneiden.
- Machen wir dem Benutzer gar nicht erst damit zu schaffen. Es gibt Möglichkeiten, eine UI-Zuschneidewerkzeug zu erstellen, damit Benutzer ihre eigenen Zuschnitte auswählen können, aber machen wir keinen zusätzlichen Schritt für sie und erledigen wir es einfach automatisch
- Schneiden wir von oben/links.
All diese Canvas-Sachen waren mir etwas zu hoch. Glücklicherweise konnte Ralph Holzmann einspringen und mir helfen, Andrea's ursprüngliches Skript anzupassen, um das Zuschneiden zu handhaben.
Zuschneiden / Größenanpassung in Aktion
Mit diesen Teilen, die bereit sind, können wir zuschneiden und die Größe ändern, indem wir unser neues Skript aufrufen, das beides tut
var fileTracker = new FileReader;
fileTracker.onload = function() {
Resample(
this.result,
256,
256,
placeNewAvatar
);
}
fileTracker.readAsDataURL(file);
placeNewAvatar ist eine benutzerdefinierte Callback-Funktion, die wir bereitstellen und die den neu skalierten Data-URI empfängt, den wir auf der Seite platzieren können.
function placeNewAvatar(data) {
$("#profile-avatar").attr("src", data);
}
Hochladen und Speichern des Avatars
Wahrscheinlich möchten Sie Ihren skalierten Avatar speichern, nicht den Original. Wissen Sie, um die Dinge schnell und den Speicherplatz gering zu halten.
Es wäre albern, einen Seitenaufruf zum Hochladen der Datei auszulösen, da wir bereits ein ausgefallenes Drag and Drop verwenden. Also schauen wir uns an, wie wir die Datei per Ajax an den Server senden. Vielleicht ist Ihr Server völlig in Ordnung damit, diesen Data-URI zu akzeptieren und zu speichern. In diesem Fall laden Sie ihn einfach hoch, wie auch immer. Vielleicht so
$.post({
url: "/your/app/save/image/whatever",
data: data
});
Aber wenn Sie eine Art Asset-Host verwenden, möchten sie wahrscheinlich eine echte Datei und keinen Data-URI. Und sie möchten, dass Sie ihnen multipart/form-data per POST senden, nicht nur einen String. Sie müssen also diesen Data-URI in eine Datei (oder "Blob") umwandeln.
function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
Sie können auch kein jQuery mehr für Ajax-Dateien verwenden, da seine Ajax-Methoden meiner Erfahrung nach keine FormData() übergeben können. Sie müssen es also manuell tun, was in Ordnung ist, da Drag and Drop ohnehin neuer ist als Ajax.
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append('file', resampledFile);
xhr.open('POST', "/your/app/save/image/whatever", true);
xhr.send(fd);
Relevante CSS-Schnipsel
Wenn Sie das Drop-Ereignis auf dem Body verfolgen wollen, sollten Sie sicherstellen, dass der Body mindestens so hoch ist wie die Seite. Andernfalls könnte es am unteren Rand einige Bereiche geben, die das Ereignis nicht annehmen.
html, body {
height: 100%;
}
Außerdem wird das Drag-Ereignis nicht nur durch Dateien ausgelöst, die Sie von außerhalb des Browserfensters ziehen, sondern auch durch das Ziehen eines bereits auf der Seite platzierten Bildes. Um dies zu verhindern, umschließe ich das Bild mit einem Div und wende ein Pseudoelement über das gesamte Div an. Dies verhindert das Ziehen dieses Bildes.
.profile-avatar-wrap {
position: relative;
}
.profile-avatar-wrap:after {
/* Drag Prevention */
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Das funktioniert auch mit dem Datei-Eingabefeld
Als Fallback können Sie auch ein Datei-Eingabefeld einfügen. Wie genau Sie damit umgehen wollen, bleibt Ihnen überlassen. Zum Beispiel, fügen Sie es bei einem Feature-Detection-Fehler ein oder stellen Sie einfach beides auf der Seite bereit.
<input type="file" id="uploader">
Alles wäre praktisch dasselbe, nur der Zugriff auf die Datei würde stattfinden
$("#uploader").on('change', function(event) {
var file = event.target.files[0];
});
Dies ist besonders relevant für mobile Geräte, wo Drag and Drop eher irrelevant ist.
Noch nicht ganz fertig
Hier ist die Demo
Ich bin mir nicht einmal sicher, wie gut die Browserunterstützung ist. Hier ist CanIUse für Drag and Drop und Canvas.
Ich bin sicher, dass ich hier nicht alles perfekt gemacht habe. Einerseits scheint es in Opera nicht zu funktionieren. Die Drag-and-Drop-Sachen scheinen zu funktionieren (es fragt Sie, ob Sie die Datei hochladen möchten), aber sie verarbeiten sich nie ganz.
Es funktioniert in Chrome/Safari/Firefox.
Andererseits habe ich die "Drop File Anywhere"-Sache durch Hinzufügen eines Pseudoelements zum Body behandelt. Manchmal wird es ein wenig "flackernd". Es ist eine Verbesserung gegenüber dem, als ich versuchte, es mit einem Div zu tun, aber nicht großartig. Ich habe auch versucht, nur eine UI-Aktion durchzuführen, wenn das dragleave-Ereignis vom Body selbst ausging.
$("body").on("dragleave", function(event) {
if (event.currentTarget == $("body")[0]) {
$("body").removeClassClass("droppable");
}
// Required for drop to work
return false;
});
Aber kein Erfolg.
Und schließlich ist der in diesem Artikel gezeigte Code nicht organisiert. Im wirklichen Leben würden Sie all das organisieren. Hier ist der organisierte Code. Wenn Sie Dinge reparieren oder verbessern können, habe ich es auf GitHub gestellt, also zögern Sie nicht, eine Pull-Anfrage einzureichen (wie).
Wie sicher ist eine solche Hochladung? Gibt es Sicherheitslücken, um die man sich Sorgen machen muss?
Sie skalieren/beschneiden auf der Clientseite und senden es dann an die Serverseite. Ohne Validierung auf der Serverseite könnte jeder alles, jedes Bild, jede Größe usw. hochladen. Meiner Meinung nach ist dieser Beitrag nur ein Beweis dafür, dass es auf der Clientseite gemacht werden kann (und für die Einsparung von Upload-Zeiten für große Bilder verwendet werden kann, wie im Beitrag erwähnt), aber Sie sollten sich nicht auf hochgeladene Dinge verlassen und nichts vertrauen, das an „/your/app/save/image/whatever“ gesendet wird.
Super! Ich werde das in naher Zukunft auf jeden Fall brauchen.
Ein kleiner Fehler in Chrome 23 unter Mac: Nachdem ich den Datei-Upload zum Ändern des Avatars verwendet habe, ziehe ich ein neues Bild per Drag & Drop. Während ich über die Dropzone ziehe, flackert die Dropzone wiederholt ein und aus. Tritt nur beim ersten Mal nach der Verwendung des Datei-Uploads auf. Keine Ahnung, was die Lösung sein könnte, aber vielleicht weiß jemand hier weiter.
Und mit "jemand hier weiß" meine ich "jemand hier weiß" ...
Ich habe denselben Fehler gefunden, jedoch Chrome 24 unter Mac, bin mir nicht sicher, was ihn verursacht?
Sobald der dragover-Zustand auf dem Body ausgelöst wird, wird ein :after-Element hinzugefügt, das dann dazu führt, dass der Body den dragover-Zustand verliert.
Epilepsie setzt ein.
Richtig. Ich erwähne das im Artikel als Problem. Es ist sogar noch schlimmer, wenn Sie meiner Erfahrung nach ein Div oder etwas Ähnliches verwenden.
Die verbleibenden Probleme sind
1) Warum zum Teufel löst ein Pseudoelement ein dragleave aus?
2) Dies geschieht *auch dann*, wenn Sie event.currentTarget überprüfen und sicherstellen, dass es sich um den Body handelt (seltsam)
3) Ist das Binden/Entbinden die Antwort? Normalerweise nicht...
Eine Sache, die ich tatsächlich verwendet habe, ist einfach etwas weniger visuell intensives zu tun, um den Drop-Bereich hervorzuheben. Selbst wenn es flackert, ist es nicht so epilepsieauslösend.
1 & 2. Vielleicht funktioniert es wie mouseover und mouseout.
Dieses Flackern mag damit zusammenhängen oder auch nicht (obwohl ich glaube, dass es so ist), aber die Hälfte der Zeit, wenn ich ein Bild fallen lasse, wird das "drop"-Ereignis nicht ausgelöst, und ich werde dabei erwischt, wie ich das Bild lade, als hätte ich einen Lesezeichen auf die Seite gezogen.
Um zufälliges Flackern in der Vergangenheit zu bekämpfen, habe ich das dragleave-Ereignis so eingestellt, dass es die Verlassen-Aktion nicht direkt aufruft, sondern stattdessen einen setTimeout mit einer Verzögerung von 200 ms verwendet, der die Verlassen-Aktion aufruft. Dann, in den dragenter- und dragover-Ereignissen, löschen Sie diesen Timeout, bevor Sie etwas anderes tun. Diese Technik habe ich aus einem Drag-and-Drop-Tutorial irgendwoher übernommen und sie hat sich gut bewährt.
Für das Problem des :after Pseudoelements spezifisch, vielleicht setzen Sie dort ein pointer-events:none drauf?
Beides tolle Ideen. Ich dränge beide.
Ich bin ziemlich sicher, dass Opera die FormData()-Funktion noch nicht unterstützt, was den Upload-Code verhindert. Nicht überraschend, glaube ich nicht, dass irgendein IE (vielleicht IE10 schon) das neue XMLHttpRequest 2 unterstützt, das Sie beim Upload verwenden. Remy Sharp erwähnt das auch in seinem Artikel: http://html5doctor.com/drag-and-drop-to-server/
Danke für den tollen Beitrag!
Haben Sie irgendwelche Statistiken über die Popularität von Drag and Drop im Vergleich zu einem regulären Datei-Chooser? Ich habe meinen Browser immer im Vollbildmodus, daher ist der Datei-Picker für mich einfacher.
Mit IE8+ Unterstützung würde es tatsächlich Sinn machen, da dies eine Kernfunktionalität ist.
Dennoch ein sehr interessanter Artikel und es ist Wahnsinn, dass wir (der Server) eine 9MP-Datei auf 100x100 Pixel skalieren müssen.
Ich versuche, dies zu einer Profilseite hinzuzufügen, an der ich gerade arbeite. Ich glaube, ich habe alles richtig gemacht. Ich habe die Dateien auf meinen Testserver hochgeladen, die Pfade sind richtig.
Wenn ich das Bild per Drag & Drop ziehe. Es hebt den Avatarbereich hervor, oder wenn ich auf die Schaltfläche "Datei auswählen" klicke, kann ich die Datei auswählen. Es lädt das Bild jedoch nicht hoch, wenn ich eines von beiden tue.
Könnte es einen Schritt geben, den ich übersehe?
Ich benutze die neueste Version von Chrome
Es funktioniert bei Safari bei mir oder meinem Bruder nicht. Ich bin auf PC, er ist auf Mac. Ich bin v. 5.1.7
Nur für Drag & Drop von Bilddateien vom OS zum Browser habe ich vor einem Jahr Folgendes getan
Demo-Link
Ich möchte es überprüfen lassen. Außerdem habe ich die DataURI-Konvertierung durch Base64-Kodierung eines Bildes verwendet, was in den src der Bilder in Ihrer Demo ebenfalls sichtbar ist. Ich denke, es ist eine schlechte schlechte Art, solche riesigen kodierten Strings zu speichern. Welche anderen Optionen sind möglich?
funktioniert super
Danke Chris :D
Ich möchte dies in eine Seite mit mehreren Avataren implementieren, die geändert werden könnten. Was muss ich ändern, um nur ein Bild per Drag & Drop ändern zu können? Zu diesem Zeitpunkt werden alle Avatare auf meiner Seite geändert.