Przechwytywanie obrazu od użytkownika

Większość przeglądarek ma dostęp do kamery użytkownika.

Wiele przeglądarek ma teraz możliwość dostępu do danych wejściowych wideo i dźwięku od użytkownika. Jednak w zależności od przeglądarki może to być pełne, dynamiczne wrażenia w ramach strony lub funkcja delegowana do innej aplikacji na urządzeniu użytkownika. Co więcej, nie wszystkie urządzenia mają kamerę. Jak więc stworzyć obraz użytkownika, który działa dobrze wszędzie?

Zacznij od prostych działań i stopniowo je rozwijaj

Jeśli chcesz stopniowo poprawiać wygodę korzystania z witryny, zacznij od czegoś, co będzie działać wszędzie. Najłatwiej jest poprosić użytkownika o wcześniej nagrany plik.

Prośba o adres URL

Jest to opcja najlepiej obsługiwana, ale najmniej satysfakcjonująca. Poproś użytkownika o podanie adresu URL i użyj go. W przypadku wyświetlania tylko obrazu działa wszędzie. Utwórz element img, ustaw src i gotowe.

Jeśli jednak chcesz w jakimś stopniu zmodyfikować obraz, sprawa się komplikuje. CORS uniemożliwia dostęp do rzeczywistych pikseli, chyba że serwer ustawi odpowiednie nagłówki i oznaczy obraz jako pochodzący z innego źródła. Jedynym praktycznym sposobem na obejście tego ograniczenia jest uruchomienie serwera proxy.

Dane wejściowe z pliku

Możesz też użyć prostego elementu danych pliku, w tym filtra accept, który wskazuje, że chcesz uwzględnić tylko pliki obrazów.

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

Ta metoda działa na wszystkich platformach. Na komputerze użytkownik zostanie poproszony o przesłanie pliku z obrazem z systemu plików. W Chrome i Safari na iOS i Androidzie ta metoda pozwoli użytkownikowi wybrać aplikację do zrobienia zdjęcia, w tym możliwość zrobienia zdjęcia bezpośrednio aparatem lub wybrania istniejącego pliku z obrazem.

Menu Androida z 2 opcjami: zrób zdjęcie i pliki Menu iOS z 3 opcjami: robienie zdjęć, biblioteka zdjęć, iCloud

Dane można następnie dołączyć do elementu <form> lub zmodyfikować za pomocą kodu JavaScript, wykradając zdarzenie onchange w elemencie wejściowym, a potem odczytując właściwość files zdarzenia target.

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

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

Właściwość files to obiekt FileList, o którym opowiem później.

Możesz też opcjonalnie dodać do elementu atrybut capture, który będzie wskazywać przeglądarce, że wolisz pobrać obraz z kamery.

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

Dodanie atrybutu capture bez wartości pozwala przeglądarce zdecydować, której kamery użyć, natomiast wartości "user" i "environment" informują przeglądarkę, aby preferowała odpowiednio przednią i tylną kamerę.

Atrybut capture działa na urządzeniach z Androidem i iOS, ale jest ignorowany na komputerach. Pamiętaj jednak, że w przypadku Androida użytkownik nie będzie już mógł wybrać istniejącej fotografii. Zamiast tego zostanie uruchomiona aplikacja aparatu systemowego.

Przeciągnij i upuść

Jeśli już dodajesz możliwość przesyłania plików, masz kilka prostych sposobów na zwiększenie wygody użytkowników.

Pierwszym jest dodanie do strony celu upuszczenia, który umożliwia użytkownikowi przeciągnięcie pliku z pulpitu lub innej aplikacji.

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

Podobnie jak w przypadku wejścia typu plik, możesz pobrać obiekt FileList z właściwości dataTransfer.files zdarzenia drop.

Obsługa zdarzenia dragover pozwala poinformować użytkownika, co stanie się po upuszczeniu pliku, za pomocą właściwości dropEffect.

Funkcja przeciągania i upuszczania istnieje od dawna i jest dobrze obsługiwana przez główne przeglądarki.

Wklej ze schowka

Ostatnim sposobem na uzyskanie istniejącego pliku graficznego jest pobranie go ze schowka. Kod jest bardzo prosty, ale wrażenia użytkownika są nieco trudniejsze do prawidłowego skonfigurowania.

<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 to kolejny obiekt FileList).

Trudność z tym polega na tym, że aby interfejs był w pełni obsługiwany w różnych przeglądarkach, element docelowy musi być zarówno możliwy do wyboru, jak i edytowalny. Zarówno <textarea>, jak i <input type="text"> spełniają te wymagania, podobnie jak elementy z atrybutem contenteditable. Są one jednak również przeznaczone do edytowania tekstu.

Jeśli nie chcesz, aby użytkownik mógł wpisywać tekst, może być trudno sprawić, aby wszystko działało prawidłowo. Triki takie jak ukryte pole wprowadzania, które jest wybierane po kliknięciu innego elementu, mogą utrudniać zachowanie dostępności.

Obsługa obiektu FileList

Ponieważ większość powyższych metod zwraca wartość FileList, warto wyjaśnić, czym ona jest.

FileList jest podobny do Array. Zawiera klucze liczbowe i właściwość length, ale nie jest faktycznie tablicą. Nie ma metod tablicy, takich jak forEach() czy pop(), i nie można jej iterować. Prawidłową tablica możesz otrzymać oczywiście przy użyciu funkcji Array.from(fileList).

Wpisy w FileList to obiekty File. Są to dokładnie te same obiekty co Blob, z tym wyjątkiem, że mają dodatkowe właściwości tylko do odczytu namelastModified.

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

W tym przykładzie zostanie znaleziony pierwszy plik o typie MIME obrazu, ale można też zaznaczyć, wkleić lub upuścić naraz wiele obrazów.

Gdy masz dostęp do pliku, możesz z nim zrobić wszystko, co chcesz. Możesz na przykład:

  • Narysuj go w elemencie <canvas>, aby móc go modyfikować.
  • Pobierz na urządzenie użytkownika.
  • Prześlij go na serwer za pomocą fetch()

Interaktywny dostęp do kamery

Teraz, gdy masz już podstawy, nadszedł czas na stopniowe ulepszanie.

Nowoczesne przeglądarki mogą uzyskać bezpośredni dostęp do kamer, co pozwala tworzyć wrażenia, które są w pełni zintegrowane ze stroną internetową, dzięki czemu użytkownik nie musi opuszczać przeglądarki.

Uzyskiwanie dostępu do aparatu

Dostęp do kamery i mikrofonu możesz uzyskać bezpośrednio, korzystając z interfejsu API w specyfikacji WebRTC o nazwie getUserMedia(). Użytkownik zostanie poproszony o dostęp do połączonych mikrofonów i kamer.

Obsługa getUserMedia() jest całkiem dobra, ale nie wszędzie jest jeszcze dostępna. Nie jest ona dostępna w przeglądarce Safari 10 ani starszej, która w chwili pisania wiadomości jest nadal w najnowszej stabilnej wersji. Firma Apple ogłosiła jednak udostępnienie tej funkcji w przeglądarce Safari 11.

Zauważenie pomocy jest jednak bardzo proste.

const supported = 'mediaDevices' in navigator;

Gdy wywołujesz funkcję getUserMedia(), musisz przekazać obiekt opisujący rodzaj multimediów, którego chcesz użyć. Te wybory nazywamy ograniczeniami. Istnieje kilka możliwych ograniczeń, takich jak preferowana kamera przednia czy tylna, czy chcesz włączyć dźwięk i jaka ma być rozdzielczość strumienia.

Aby jednak uzyskać dane z kamery, potrzebujesz tylko jednego ograniczenia, a jest nim video: true.

W przypadku powodzenia interfejs API zwróci obiekt MediaStream zawierający dane z aparatu. Możesz go załączyć do elementu <video> i odtworzyć, aby wyświetlić podgląd w czasie rzeczywistym, lub do elementu <canvas>, aby uzyskać zrzut ekranu.

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

Samo w sobie nie jest zbyt przydatne. Możesz tylko pobrać dane wideo i je odtworzyć. Jeśli chcesz uzyskać obraz, musisz wykonać trochę dodatkowej pracy.

Zrób zrzut

Najlepszą opcją uzyskania obrazu jest przeniesienie kadru z filmu na płótno.

W przeciwieństwie do interfejsu Web Audio API nie ma specjalnego interfejsu API do przetwarzania strumieni dla filmów w internecie, więc aby zrobić zdjęcie kamerą użytkownika, musisz uzbroić się w trochę ataku hakerów.

Proces wygląda następująco:

  1. Utwórz obiekt kanwy, który będzie zawierać ramkę z kamery.
  2. Uzyskiwanie dostępu do strumienia z kamery
  3. Dołącz go do elementu wideo.
  4. Jeśli chcesz uchwycić konkretny kadr, dodaj dane z elementu wideo do obiektu na osi czasu za pomocą drawImage().
<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>

Gdy masz dane z kamery zapisane na płótnie, możesz z nimi zrobić wiele rzeczy. Możesz:

  • Prześlij film bezpośrednio na serwer
  • Przechowywanie danych lokalnie
  • Zastosowanie efektów do obrazu

Wskazówki

Zatrzymuj transmisję z kamery, gdy nie jest potrzebna

Warto przestać używać kamery, gdy nie jest już potrzebna. Pozwoli to nie tylko zaoszczędzić baterię i moc obliczeniową, ale też zwiększy zaufanie użytkowników do aplikacji.

Aby zatrzymać dostęp do kamery, możesz po prostu wywołać funkcję stop() w przypadku każdej ścieżki wideo dla strumienia zwracanego przez funkcję getUserMedia().

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

Prośba o dostęp do kamery

Jeśli użytkownik nie zezwolił wcześniej Twojej witrynie na dostęp do aparatu, to gdy wywołasz funkcję getUserMedia(), przeglądarka poprosi użytkownika o przyznanie Twojej witrynie dostępu do aparatu.

Użytkownicy nie lubią otrzymywać próśb o dostęp do zaawansowanych urządzeń na swoim komputerze i często je blokują lub zignorują, jeśli nie zrozumieją kontekstu, dla którego utworzono prompt. Najlepiej prosić o dostęp do aparatu tylko wtedy, gdy jest to konieczne. Gdy użytkownik przyzna dostęp, nie będzie już o to pytany. Jeśli jednak użytkownik odrzuci dostęp, nie będzie można go uzyskać ponownie, chyba że użytkownik ręcznie zmieni ustawienia uprawnień aparatu.

Zgodność

Więcej informacji o wdrożeniu w przeglądarce mobilnej i komputerowej:

Zalecamy też używanie pliku adapter.js, aby chronić aplikacje przed zmianami specyfikacji WebRTC i różnicami w prefiksach.

Prześlij opinię