Aufnahme eines Bilds des Nutzers

Die meisten Browser können auf die Kamera des Nutzers zugreifen.

Viele Browser können jetzt auf Video- und Audioeingaben des Nutzers zugreifen. Je nach Browser kann es sich jedoch um eine vollständig dynamische und Inline-Erfahrung handeln oder die Aufgabe kann an eine andere App auf dem Gerät des Nutzers delegiert werden. Außerdem haben nicht alle Geräte eine Kamera. Wie können Sie also eine Umgebung schaffen, in der ein von Nutzern generiertes Bild überall gut funktioniert?

Einfach und schrittweise beginnen

Wenn Sie Ihr Nutzererlebnis schrittweise verbessern möchten, müssen Sie mit etwas beginnen, das überall funktioniert. Am einfachsten ist es, den Nutzer einfach nach einer vorab aufgezeichneten Datei zu fragen.

URL anfordern

Dies ist die am besten unterstützte, aber am wenigsten zufriedenstellende Option. Bitte den Nutzer, dir eine URL zu geben, und verwende diese dann. Wenn Sie nur das Bild anzeigen möchten, funktioniert das überall. Erstellen Sie ein img-Element, legen Sie src fest und das war's.

Wenn Sie das Bild jedoch in irgendeiner Weise bearbeiten möchten, wird es etwas komplizierter. CORS verhindert den Zugriff auf die tatsächlichen Pixel, es sei denn, der Server legt die entsprechenden Header fest und Sie kennzeichnen das Bild als „crossorigin“. Die einzige praktische Möglichkeit, dies zu umgehen, ist die Verwendung eines Proxyservers.

Dateieingabe

Sie können auch ein einfaches Dateieingabeelement verwenden, einschließlich eines accept-Filters, der angibt, dass Sie nur Bilddateien möchten.

<input type="file" accept="image/*" />

Diese Methode funktioniert auf allen Plattformen. Auf dem Computer wird der Nutzer aufgefordert, eine Bilddatei aus dem Dateisystem hochzuladen. In Chrome und Safari unter iOS und Android kann der Nutzer mit dieser Methode auswählen, mit welcher App das Bild aufgenommen werden soll. Er kann auch ein Foto direkt mit der Kamera aufnehmen oder eine vorhandene Bilddatei auswählen.

Ein Android-Menü mit zwei Optionen: „Bild aufnehmen“ und „Dateien“ Ein iOS-Menü mit drei Optionen: „Foto aufnehmen“, „Fotomediathek“, „iCloud“

Die Daten können dann an ein <form> angehängt oder mit JavaScript manipuliert werden. Dazu müssen Sie auf dem Eingabeelement auf ein onchange-Ereignis warten und dann die files-Eigenschaft des Ereignisses target lesen.

<input type="file" accept="image/*" id="file-input" />
<script>
  const fileInput = document.getElementById('file-input');

  fileInput.addEventListener('change', (e) =>
    doSomethingWithFiles(e.target.files),
  );
</script>

Das Attribut files ist ein FileList-Objekt, auf das ich später noch genauer eingehen werde.

Sie können dem Element optional auch das Attribut capture hinzufügen, um dem Browser mitzuteilen, dass Sie bevorzugt ein Bild von der Kamera erhalten möchten.

<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />

Wenn Sie das Attribut capture ohne Wert hinzufügen, kann der Browser entscheiden, welche Kamera verwendet werden soll. Mit den Werten "user" und "environment" wird der Browser angewiesen, die Vorder- bzw. Rückkamera zu bevorzugen.

Das Attribut capture funktioniert auf Android- und iOS-Geräten, wird aber auf Computern ignoriert. Unter Android bedeutet das jedoch, dass der Nutzer keine Möglichkeit mehr hat, ein vorhandenes Bild auszuwählen. Stattdessen wird die Systemkamera-App direkt gestartet.

Drag-and-Drop

Wenn Sie bereits die Möglichkeit zum Hochladen einer Datei hinzufügen, gibt es einige einfache Möglichkeiten, die Nutzerfreundlichkeit zu verbessern.

Die erste Möglichkeit besteht darin, Ihrer Seite ein Ablageziel hinzuzufügen, in das der Nutzer eine Datei vom Desktop oder aus einer anderen Anwendung ziehen kann.

<div id="target">You can drag an image file here</div>
<script>
  const target = document.getElementById('target');

  target.addEventListener('drop', (e) => {
    e.stopPropagation();
    e.preventDefault();

    doSomethingWithFiles(e.dataTransfer.files);
  });

  target.addEventListener('dragover', (e) => {
    e.stopPropagation();
    e.preventDefault();

    e.dataTransfer.dropEffect = 'copy';
  });
</script>

Ähnlich wie bei der Dateieingabe können Sie ein FileList-Objekt über die dataTransfer.files-Eigenschaft des drop-Ereignisses abrufen.

Mit dem Ereignis-Handler dragover können Sie dem Nutzer mithilfe der Eigenschaft dropEffect signalisieren, was passiert, wenn er die Datei ablegt.

Drag-and-drop gibt es schon lange und es wird von den gängigen Browsern gut unterstützt.

Aus Zwischenablage einfügen

Die letzte Möglichkeit, eine vorhandene Bilddatei zu erhalten, ist über die Zwischenablage. Der Code dafür ist sehr einfach, aber die Nutzerfreundlichkeit ist etwas schwieriger zu optimieren.

<textarea id="target">Paste an image here</textarea>
<script>
  const target = document.getElementById('target');

  target.addEventListener('paste', (e) => {
    e.preventDefault();
    doSomethingWithFiles(e.clipboardData.files);
  });
</script>

(e.clipboardData.files ist ein weiteres FileList-Objekt.)

Der schwierige Teil bei der Zwischenablage-API ist, dass das Zielelement für die vollständige browserübergreifende Unterstützung sowohl auswählbar als auch bearbeitbar sein muss. Sowohl <textarea> als auch <input type="text"> kommen hier infrage, ebenso wie Elemente mit dem Attribut contenteditable. Diese sind aber natürlich auch für die Bearbeitung von Text konzipiert.

Wenn der Nutzer keinen Text eingeben soll, kann es schwierig sein, dies reibungslos zu gestalten. Tricks wie ein verborgenes Eingabefeld, das ausgewählt wird, wenn Sie auf ein anderes Element klicken, können die Barrierefreiheit erschweren.

FileList-Objekt verarbeiten

Da bei den meisten der oben genannten Methoden ein FileList entsteht, möchte ich kurz darauf eingehen.

Ein FileList ähnelt einem Array. Es hat numerische Schlüssel und ein length-Attribut, ist aber nicht wirklich ein Array. Es gibt keine Array-Methoden wie forEach() oder pop() und es ist nicht iterierbar. Mit Array.from(fileList) können Sie natürlich ein echtes Array abrufen.

Die Einträge von FileList sind File-Objekte. Sie sind mit Blob-Objekten identisch, haben aber zusätzliche schreibgeschützte Eigenschaften für name und lastModified.

<img id="output" />
<script>
  const output = document.getElementById('output');

  function doSomethingWithFiles(fileList) {
    let file = null;

    for (let i = 0; i < fileList.length; i++) {
      if (fileList[i].type.match(/^image\//)) {
        file = fileList[i];
        break;
      }
    }

    if (file !== null) {
      output.src = URL.createObjectURL(file);
    }
  }
</script>

In diesem Beispiel wird die erste Datei mit einem Bild-MIME-Typ gesucht. Es könnten aber auch mehrere Bilder gleichzeitig ausgewählt, eingefügt oder per Drag-and-drop verschoben werden.

Sobald Sie Zugriff auf die Datei haben, können Sie damit tun, was Sie möchten. Beispielsweise können Sie…

  • Zeichnen Sie es in ein <canvas>-Element, damit Sie es bearbeiten können.
  • Auf das Gerät des Nutzers herunterladen
  • Laden Sie sie mit fetch() auf einen Server hoch.

Interaktiver Zugriff auf die Kamera

Nachdem Sie die Grundlagen geschaffen haben, ist es an der Zeit, die Website schrittweise zu optimieren.

Moderne Browser können direkt auf Kameras zugreifen. So können Sie Erlebnisse schaffen, die vollständig in die Webseite integriert sind, sodass der Nutzer den Browser nie verlassen muss.

Zugriff auf die Kamera erhalten

Sie können direkt über eine API in der WebRTC-Spezifikation namens getUserMedia() auf eine Kamera und ein Mikrofon zugreifen. Der Nutzer wird dann aufgefordert, den Zugriff auf seine verbundenen Mikrofone und Kameras zu gewähren.

Die Unterstützung für getUserMedia() ist recht gut, aber noch nicht überall verfügbar. Insbesondere ist sie nicht in Safari 10 oder niedriger verfügbar, was zum Zeitpunkt der Erstellung dieses Dokuments noch die aktuelle stabile Version ist. Apple hat jedoch angekündigt, dass die Funktion in Safari 11 verfügbar sein wird.

Die Unterstützung ist jedoch sehr einfach zu erkennen.

const supported = 'mediaDevices' in navigator;

Wenn Sie getUserMedia() aufrufen, müssen Sie ein Objekt übergeben, das beschreibt, welche Art von Media Sie möchten. Diese Auswahlmöglichkeiten werden als Einschränkungen bezeichnet. Es gibt verschiedene mögliche Einschränkungen, z. B. ob Sie eine nach vorn oder nach hinten gerichtete Kamera bevorzugen, ob Sie Audio wünschen und welche Auflösung Sie für den Stream bevorzugen.

Um Daten von der Kamera zu erhalten, benötigen Sie jedoch nur eine Einschränkung, nämlich video: true.

Bei Erfolg gibt die API ein MediaStream zurück, das Daten von der Kamera enthält. Sie können es dann entweder an ein <video>-Element anhängen und abspielen, um eine Echtzeitvorschau zu erhalten, oder an ein <canvas>, um einen Snapshot zu erhalten.

<video id="player" controls playsinline autoplay></video>
<script>
  const player = document.getElementById('player');

  const constraints = {
    video: true,
  };

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

Allein ist das nicht so nützlich. Sie können nur die Videodaten abrufen und wiedergeben. Wenn Sie ein Bild erhalten möchten, müssen Sie etwas mehr tun.

Snapshot erstellen

Die beste unterstützte Option zum Abrufen eines Bildes besteht darin, einen Frame aus dem Video auf eine Arbeitsfläche zu zeichnen.

Im Gegensatz zur Web Audio API gibt es keine spezielle API für die Streamverarbeitung von Videos im Web. Sie müssen also auf einen kleinen Trick zurückgreifen, um einen Schnappschuss von der Kamera des Nutzers aufzunehmen.

So läuft der Vorgang ab:

  1. Erstellen Sie ein Canvas-Objekt, das den Frame von der Kamera enthält.
  2. Zugriff auf den Kamerastream erhalten
  3. An ein Videoelement anhängen
  4. Wenn Sie ein bestimmtes Frame erfassen möchten, fügen Sie die Daten aus dem Videoelement mit drawImage() in ein Canvas-Objekt ein.
<video id="player" controls playsinline autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    // Draw the video frame to the canvas.
    context.drawImage(player, 0, 0, canvas.width, canvas.height);
  });

  // Attach the video stream to the video element and autoplay.
  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

Sobald Sie Daten von der Kamera im Arbeitsbereich gespeichert haben, können Sie damit viel anfangen. Sie haben folgende Möglichkeiten:

  • Direkt auf den Server hochladen
  • Lokal speichern
  • Coole Effekte auf das Bild anwenden

Tipps

Streaming von der Kamera beenden, wenn es nicht benötigt wird

Es empfiehlt sich, die Kamera nicht mehr zu verwenden, wenn Sie sie nicht mehr benötigen. Das spart nicht nur Akku und Rechenleistung, sondern gibt Nutzern auch Vertrauen in Ihre Anwendung.

Um den Zugriff auf die Kamera zu beenden, können Sie einfach stop() für jeden Videotrack des von getUserMedia() zurückgegebenen Streams aufrufen.

<video id="player" controls playsinline autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    context.drawImage(player, 0, 0, canvas.width, canvas.height);

    // Stop all video streams.
    player.srcObject.getVideoTracks().forEach(track => track.stop());
  });

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    // Attach the video stream to the video element and autoplay.
    player.srcObject = stream;
  });
</script>

Verantwortungsvoll um Erlaubnis bitten, die Kamera zu verwenden

Wenn der Nutzer Ihrer Website noch keinen Zugriff auf die Kamera gewährt hat, wird er vom Browser aufgefordert, Ihrer Website die Berechtigung für die Kamera zu erteilen, sobald Sie getUserMedia() aufrufen.

Nutzer sind genervt, wenn sie auf ihrem Gerät aufgefordert werden, den Zugriff auf leistungsstarke Geräte zu gewähren. Sie blockieren die Anfrage häufig oder ignorieren sie, wenn sie den Kontext, in dem die Aufforderung erstellt wurde, nicht verstehen. Es empfiehlt sich, den Zugriff auf die Kamera erst dann anzufordern, wenn er zum ersten Mal benötigt wird. Nachdem der Nutzer den Zugriff gewährt hat, wird er nicht noch einmal dazu aufgefordert. Wenn der Nutzer den Zugriff jedoch ablehnt, können Sie ihn nicht noch einmal anfordern, es sei denn, er ändert die Einstellungen für die Kameraberechtigung manuell.

Kompatibilität

Weitere Informationen zur Implementierung für Mobilgeräte und Desktopbrowser:

Wir empfehlen außerdem, das adapter.js-Shim zu verwenden, um Apps vor Änderungen der WebRTC-Spezifikation und Präfixunterschieden zu schützen.

Feedback