Захват изображения от пользователя

Большинство браузеров могут получить доступ к камере пользователя.

Многие браузеры теперь имеют возможность доступа к видео- и аудиовходу пользователя. Однако, в зависимости от браузера, это может быть полностью динамический и встроенный интерфейс или его можно делегировать другому приложению на устройстве пользователя. Кроме того, не в каждом устройстве есть камера. Так как же создать интерфейс, использующий созданное пользователем изображение, которое будет хорошо работать везде?

Начните с простого и постепенно

Если вы хотите постепенно улучшать свой опыт, вам нужно начать с чего-то, что работает везде. Самый простой способ — просто попросить пользователя предоставить заранее записанный файл.

Запросить URL-адрес

Это наиболее поддерживаемый, но наименее удовлетворительный вариант. Попросите пользователя предоставить вам URL-адрес, а затем используйте его. Для простого отображения изображения это работает везде. Создайте элемент img , установите src и все готово.

Однако, если вы хотите каким-либо образом манипулировать изображением, все немного сложнее. CORS не позволяет вам получить доступ к реальным пикселям, если сервер не установит соответствующие заголовки и вы не пометите изображение как перекрестное ; единственный практический способ обойти это — запустить прокси-сервер.

Ввод файла

Вы также можете использовать простой элемент ввода файла, включая фильтр accept , который указывает, что вам нужны только файлы изображений.

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

Этот метод работает на всех платформах. На рабочем столе пользователю будет предложено загрузить файл изображения из файловой системы. В Chrome и Safari на iOS и Android этот метод предоставит пользователю выбор, какое приложение использовать для захвата изображения, включая возможность сделать снимок непосредственно с помощью камеры или выбрать существующий файл изображения.

Меню Android с двумя опциями: захват изображения и файлов.Меню iOS с тремя опциями: сделать фото, библиотека фотографий, iCloud.

Затем данные можно прикрепить к <form> или манипулировать ими с помощью JavaScript, прослушивая событие onchange во входном элементе и затем читая свойство files 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>

Свойство files представляет собой объект FileList , о котором я расскажу подробнее позже.

Вы также можете при желании добавить к элементу атрибут capture , который указывает браузеру, что вы предпочитаете получать изображение с камеры.

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

Добавление атрибута capture без значения позволяет браузеру решать, какую камеру использовать, а значения "user" и "environment" сообщают браузеру, что он предпочитает переднюю и заднюю камеры соответственно.

Атрибут capture работает на Android и iOS, но игнорируется на настольном компьютере. Однако имейте в виду, что на Android это означает, что у пользователя больше не будет возможности выбрать существующее изображение. Вместо этого приложение системной камеры будет запущено напрямую.

Перетащите

Если вы уже добавляете возможность загрузки файла, есть несколько простых способов сделать пользовательский опыт немного богаче.

Первый — добавить на страницу цель перетаскивания, которая позволит пользователю перетаскивать файл с рабочего стола или из другого приложения.

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

Подобно вводу файла, вы можете получить объект FileList из свойства dataTransfer.files события drop ;

Обработчик событий dragover позволяет вам сигнализировать пользователю, что произойдет, когда он удалит файл, используя свойство dropEffect .

Перетаскивание существует уже давно и хорошо поддерживается основными браузерами.

Вставить из буфера обмена

Последний способ получить существующий файл изображения — из буфера обмена. Код для этого очень прост, но разобраться в пользовательском опыте немного сложнее.

<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 — это еще один объект FileList .)

Сложная часть API буфера обмена заключается в том, что для полной кроссбраузерной поддержки целевой элемент должен быть доступным как для выбора, так и для редактирования. И <textarea> , и <input type="text"> подходят здесь, как и элементы с атрибутом contenteditable . Но они также явно предназначены для редактирования текста.

Может быть сложно обеспечить бесперебойную работу, если вы не хотите, чтобы пользователь мог вводить текст. Такие трюки, как скрытый ввод, который выбирается при нажатии на какой-либо другой элемент, могут затруднить поддержание доступности.

Обработка объекта FileList

Поскольку большинство вышеперечисленных методов создают FileList , мне следует немного рассказать о том, что это такое.

FileList похож на Array . Он имеет числовые клавиши и свойство length , но на самом деле это не массив. Здесь нет методов массива, таких как forEach() или pop() , и он не является итеративным. Конечно, вы можете получить настоящий массив, используя Array.from(fileList) .

Записи FileList являются объектами File . Они точно такие же, как объекты Blob за исключением того, что у них есть дополнительное name и свойства 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>

В этом примере выполняется поиск первого файла с MIME-типом изображения, но он также может обрабатывать одновременное выделение, вставку или удаление нескольких изображений.

Получив доступ к файлу, вы сможете делать с ним все, что захотите. Например, вы можете:

  • Нарисуйте его в элементе <canvas> , чтобы можно было манипулировать им.
  • Загрузите его на устройство пользователя
  • Загрузите его на сервер с помощью fetch()

Доступ к камере в интерактивном режиме

Теперь, когда вы изучили свои основы, пришло время постепенно улучшаться!

Современные браузеры могут получить прямой доступ к камерам, что позволяет создавать возможности, полностью интегрированные с веб-страницей, поэтому пользователю никогда не придется покидать браузер.

Получите доступ к камере

Вы можете напрямую получить доступ к камере и микрофону, используя API в спецификации WebRTC под названием getUserMedia() . При этом пользователю будет предложено получить доступ к подключенным микрофонам и камерам.

Поддержка getUserMedia() довольно хороша, но пока не везде. В частности, он недоступен в Safari 10 или более ранней версии, которая на момент написания все еще является последней стабильной версией. Однако Apple объявила , что она будет доступна в Safari 11.

Однако обнаружить поддержку очень просто.

const supported = 'mediaDevices' in navigator;

Когда вы вызываете getUserMedia() , вам необходимо передать объект, описывающий, какой тип мультимедиа вы хотите. Этот выбор называется ограничениями. Существует несколько возможных ограничений, например, предпочитаете ли вы переднюю или заднюю камеру, хотите ли вы звук и предпочитаемое вами разрешение потока.

Однако, чтобы получить данные с камеры, вам нужно только одно ограничение — video: true .

В случае успеха API вернет MediaStream , содержащий данные с камеры, и затем вы можете либо прикрепить его к элементу <video> и воспроизвести его, чтобы отобразить предварительный просмотр в реальном времени, либо прикрепить его к <canvas> чтобы получить снимок. .

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

  const constraints = {
    video: true,
  };

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

Само по себе это не так уж и полезно. Все, что вы можете сделать, это взять видеоданные и воспроизвести их. Если вы хотите получить изображение, вам придется проделать небольшую дополнительную работу.

Сделать снимок

Лучший поддерживаемый вариант получения изображения — нарисовать кадр из видео на холсте.

В отличие от API веб-аудио, в Интернете не существует специального API обработки потока видео, поэтому вам придется прибегнуть к небольшому хакерству, чтобы сделать снимок с камеры пользователя.

Процесс выглядит следующим образом:

  1. Создайте объект холста, который будет содержать кадр с камеры.
  2. Получите доступ к трансляции с камеры
  3. Прикрепите его к видеоэлементу
  4. Если вы хотите запечатлеть точный кадр, добавьте данные из видеоэлемента в объект холста с помощью drawImage() .
<video id="player" controls 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>

Если у вас есть данные с камеры, сохраненные на холсте, вы можете делать с ними многое. Вы могли бы:

  • Загрузите его прямо на сервер
  • Храните его локально
  • Примените к изображению необычные эффекты

Советы

Остановить потоковую передачу с камеры, когда она не нужна

Хорошей практикой является прекращение использования камеры, когда она вам больше не нужна. Это не только сэкономит заряд батареи и вычислительную мощность, но и придаст пользователям уверенность в вашем приложении.

Чтобы прекратить доступ к камере, вы можете просто вызвать stop() на каждой видеодорожке для потока, возвращаемого getUserMedia() .

<video id="player" controls 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>

Спросите разрешения использовать камеру ответственно

Если пользователь ранее не предоставил вашему сайту доступ к камере, то в тот момент, когда вы вызываете getUserMedia() браузер предложит пользователю предоставить вашему сайту разрешение на использование камеры.

Пользователи ненавидят, когда им предлагают доступ к мощным устройствам на их компьютере, и они часто блокируют запрос или игнорируют его, если не понимают контекст, для которого был создан запрос. Лучше всего запрашивать доступ к камере только тогда, когда это необходимо. После того как пользователь предоставил доступ, его больше не будут спрашивать. Однако если пользователь отклоняет доступ, вы не сможете получить доступ снова, если он не изменит настройки разрешений камеры вручную.

Совместимость

Дополнительная информация о реализации браузера для мобильных и настольных компьютеров:

Мы также рекомендуем использовать прокладку адаптера.js для защиты приложений от изменений спецификаций WebRTC и различий в префиксах.

Обратная связь