Большинство браузеров могут получить доступ к камере пользователя.
Многие браузеры теперь имеют возможность доступа к видео- и аудиовходу пользователя. Однако, в зависимости от браузера, это может быть полностью динамический и встроенный опыт или он может быть делегирован другому приложению на устройстве пользователя. Кроме того, не на каждом устройстве есть камера. Так как же создать опыт, который использует созданное пользователем изображение, которое будет хорошо работать везде?
Начните с простого и постепенного
Если вы хотите постепенно улучшать свой опыт, вам нужно начать с чего-то, что работает везде. Самый простой способ — просто попросить пользователя предоставить предварительно записанный файл.
Запросить URL
Это наиболее поддерживаемый, но наименее удовлетворительный вариант. Заставьте пользователя предоставить вам URL, а затем используйте его. Для простого отображения изображения это работает везде. Создайте элемент img
, задайте src
и все готово.
Хотя, если вы хотите как-то манипулировать изображением, все немного сложнее. CORS не позволяет вам получить доступ к реальным пикселям, если сервер не установит соответствующие заголовки и вы не пометите изображение как crossorigin ; единственный практичный способ обойти это — запустить прокси-сервер.
Ввод файла
Вы также можете использовать простой элемент ввода файлов, включая фильтр accept
, который указывает, что вам нужны только файлы изображений.
<input type="file" accept="image/*" />
Этот метод работает на всех платформах. На рабочем столе он предложит пользователю загрузить файл изображения из файловой системы. В Chrome и Safari на iOS и Android этот метод предоставит пользователю выбор, какое приложение использовать для захвата изображения, включая возможность сделать снимок непосредственно камерой или выбрать существующий файл изображения.
Затем данные можно прикрепить к <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 с помощью 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 playsinline autoplay></video>
<script>
const player = document.getElementById('player');
const constraints = {
video: true,
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
player.srcObject = stream;
});
</script>
Само по себе это не так уж и полезно. Все, что вы можете сделать, это взять видеоданные и воспроизвести их. Если вы хотите получить изображение, вам придется проделать немного дополнительной работы.
Сделать снимок
Лучший поддерживаемый вариант получения изображения — это перенести кадр из видео на холст.
В отличие от API веб-аудио, в Интернете нет специального API для потоковой обработки видео, поэтому вам придется прибегнуть к небольшим уловкам, чтобы сделать снимок с камеры пользователя.
Процесс выглядит следующим образом:
- Создайте объект холста, который будет содержать кадр с камеры.
- Получите доступ к трансляции с камеры
- Прикрепите его к видеоэлементу
- Если вы хотите захватить точный кадр, добавьте данные из элемента video в объект canvas с помощью
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>
После того, как вы сохранили данные с камеры в холсте, вы можете делать с ними много вещей. Вы можете:
- Загрузите его прямо на сервер
- Хранить локально
- Примените к изображению необычные эффекты
Советы
Остановите трансляцию с камеры, когда она не нужна
Хорошей практикой является прекращение использования камеры, когда она вам больше не нужна. Это не только сэкономит заряд батареи и вычислительную мощность, но и придаст пользователям уверенность в вашем приложении.
Чтобы остановить доступ к камере, можно просто вызвать stop()
для каждой видеодорожки потока, возвращаемого 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>
Попросите разрешения использовать камеру ответственно
Если пользователь ранее не предоставил вашему сайту доступ к камере, то в тот момент, когда вы вызовете getUserMedia()
браузер предложит пользователю предоставить вашему сайту разрешение на доступ к камере.
Пользователи ненавидят, когда им предлагают доступ к мощным устройствам на их компьютере, и они часто блокируют запрос или игнорируют его, если не понимают контекста, для которого был создан запрос. Лучше всего запрашивать доступ к камере только при первой необходимости. После того, как пользователь предоставил доступ, его больше не будут спрашивать. Однако, если пользователь отклонит доступ, вы не сможете получить доступ снова, если только вручную не измените настройки разрешений камеры.
Совместимость
Дополнительная информация о реализации в мобильных и настольных браузерах:
Мы также рекомендуем использовать оболочку adapter.js для защиты приложений от изменений спецификаций WebRTC и различий префиксов.