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

Представление будет настолько простым, что оно будет содержать один снимок внизу по центру, а посередине содержимое камеры будет отображаться с использованием элемента холста.

<canvas class="camera">
    
  </canvas>
  <div class="capture">
    
  </div>

Теперь в файле/теге css мы начнем украшать наши элементы:

.camera{
   position: fixed;
   left: 0;
   top: 0;
   width: 100%;
   height: 100%;
}

.capture{
   position: fixed;
   bottom: 50px;
   left: calc(50% - 25px);
   width: 50px;
   height: 50px;
   border-radius: 50%;
   z-index: 2;
   background-color: rgba(255,255,255,.8);
}

Совершенно минималистично!

Теперь приступим к настоящей части кодирования!

Подготовка рендерера

Рендерер — это в значительной степени бумага, на которой можно свободно рисовать. В нашем случае мы будем рисовать содержимое камеры на элементе холста. Элементом холста является бумага, и для рисования на бумаге мы используем разные инструменты, также для рисования на холсте нам нужен контекст (среда и инструменты). Итак, давайте инициируем этот контекст:

let canvas = document.querySelector('camera');
let ctx = canvas.getContext('2d');

«2d» означает, что мы будем рисовать простые 2D-рисунки. Мы также можем рисовать 3D-изображения с помощью webgl.

Теперь мы можем рисовать на холсте, используя эти функции:

ctx.moveTo(10,10);
// move an imaginary pen to certain 
// coordinations
ctx.lineTo(50,50); 
// drawing a line from x10,y10 to x50,y50
// OR
ctx.drawImage(image,50,50);
// to draw the given 'image' on the x50,y50
....

Доступ к устройству камеры

Чтобы получить доступ к камере, вы сначала запрашиваете разрешение пользователя. Это будет состоять из двух шагов, первый — создать объект видео, который будет представлять контейнер для нашего отображаемого видео:

var video = document.createElement('video');

Затем мы должны запросить разрешение пользователя, и это будет использовать объект 'navigator' 'mediaDevices' в нашем коде, используя метод 'getUserMedia':

navigator.mediaDevices.getUserMedia({ video: true, audio: false}).then(
          function(rawData){
            video.srcObject = rawData;
            video.volume = 0;
            video.play();
            video.onloadeddata = function(){
                render();
            }
            
          })
          .catch(
            function(err){
              alert(err)
            })

Мы указали в качестве параметров внутри нашего 'getUserMedia' вызов атрибута видео как истинного и атрибута аудио как ложного, что означает, что мы собираемся вычитать видео только с устройства камеры. Затем мы присвоили фрагменты rawData, возвращенные обратным вызовом, нашему видео srcObject, установили громкость на 0, затем дождались загрузки данных с помощью eventListener(onloadeddata), чтобы мы могли выполнить задачу рендеринга, которую мы определили как функцию с именем «рендеринг». Итак, теперь мы должны подготовить нашу функцию рендеринга:

function render(){
   ctx.drawImage(video,0,0,canvas.width,canvas.height);
}

Что ж, никогда не было так просто, вызов 'drawImage' просто рисует медиаданные, переданные внутри (изображение, видео) по координатам x=0, y=0, width=canvas.width и height=canvas.height, очень просто.

На данный момент код, который мы написали выше, отрисовывает только первый кадр, снятый с помощью камеры, но мы хотим, чтобы камера всегда захватывала входящее видео, поскольку нам нужно снова и снова вызывать функцию рендеринга. Только добавив одну маленькую строку:

function render(){
   ctx.drawImage(video,0,0,canvas.width,canvas.height);
   requestAnimationFrame(render);
}

Да… это работает очень хорошо!

Подождите секунду, но мы пока не можем делать снимки! Именно этим мы и займемся в следующей части.

фото

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

let capture = document.querySelector('.capture');
capture.addEventListener('click',function(){
    takePic();
})

function takePic(){
   const offscreen = new OffscreenCanvas(video.videoWidth, video.videoHeight);
   let ctx = offscreen.getContext('2d');
   ctx.drawImage(video,0,0,video.videoWidth,video.videoHeight);
   offscreen.convertToBlob().then(blob=> {
      // generating download
      let a = document.createElement('a');
      a.download = 'picture.png';
      a.href = URL.createObjectURL(blob);
      a.click();
   });
}

Так что теперь это будет работать как по волшебству ✨.

Сначала мы получаем кнопку из DOM, затем прослушиваем событие click, поэтому после нажатия на нее создается закадровый холст (так же, как холст, но не отображаемый в DOM), мы рисуем на нем видео, затем конвертируем его в blob (двоичные данные), затем с помощью элемента привязки (тега) генерируем загрузку blob(URL.createObjectURL(blob)).

Доработка

Давайте соберем весь наш код в один блок, используя подход ООП:

function Camera(){
   this.video = null;
   this.canvas = null;
   this.ctx = null;
   this.capture = null;
   this.af = null;
   this.id = 0;
}
Camera.prototype = {
   init : function(){
      var _this = this;
      this.video = document.createElement('video');
      this.canvas = document.querySelector('.camera');
      this.canvas.width = window.innerWidth;
      this.canvas.height = window.innerHeight;
      this.ctx = this.canvas.getContext('2d');
      this.capture = document.querySelector('.capture');
      
      this.capture.addEventListener('click',this.takePic.bind(this));
     
      this.requestAccess();
   },
   requestAccess : function(){
      var _this = this;
      navigator.mediaDevices.getUserMedia({ video: true, audio: false}).then(
         function(rawData){
            _this.video.srcObject = rawData;
            _this.video.volume = 0;
            _this.video.play();
            _this.video.onloadeddata = function(){
                _this.af = requestAnimationFrame(_this.render.bind(_this));
            }
            
          })
          .catch(
            function(err){
              alert(err)
            })
   },
   render : function(){
       this.ctx.drawImage(this.video,0,0,this.canvas.width,this.canvas.height);
       this.af = requestAnimationFrame(this.render.bind(this));
   },
   takePic : function(){
       var _this = this;
       var canvas = new OffscreenCanvas(
                     this.video.videoWidth,
                     this.video.videoHeight);
       let ctx = canvas.getContext('2d');
        
       ctx.drawImage(this.video,0,0,this.video.videoWidth,this.video.videoHeight);
       canvas.convertToBlob().then(blob=>{
           let a = document.createElement('a');
           _this.id ++;
           let name = 'new_image' + _this.id + '.png';
           let dataUrl = URL.createObjectURL(blob);
           a.download = name;
           a.href = dataUrl;
           a.click();
          });
   }
}
    
var cam = new Camera();
cam.init();

А вот и наша камера… 🎉 тада