HTML5 Drag and Drop Avatar Changer mit Größenanpassung und Zuschneiden

Avatar of Chris Coyier
Chris Coyier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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.

Demo ansehen

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?

  1. Nehmen wir das Zuschneiden.
  2. 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
  3. 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

Demo ansehen

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).