<script>
    class SimpleCamera extends HTMLElement {

    constructor() {
      super();
    }

    connectedCallback() {
      const shadow = this.attachShadow({
        mode: 'open'
      })

      this.videoElement = document.createElement('video')
      this.canvasElement = document.createElement('canvas')
      this.videoElement.setAttribute('playsinline', true)
      this.canvasElement.style.display = 'none'

      shadow.appendChild(this.videoElement)
      shadow.appendChild(this.canvasElement)
    }

    open(constraints) {
      return navigator.mediaDevices.getUserMedia(constraints).then((mediaStream) => {
        this.videoElement.srcObject = mediaStream
        console.log(mediaStream)
        this.videoElement.onloadedmetadata = (e) => {
          this.videoElement.play()
        }
      })
    }
    
    _drawImage() {
      const imageWidth = this.videoElement.videoWidth
      const imageHeight = this.videoElement.videoHeight

      const context = this.canvasElement.getContext('2d')
      this.canvasElement.width = imageWidth
      this.canvasElement.height = imageHeight

      context.drawImage(this.videoElement, 0, 0, imageWidth, imageHeight)

      return {
        imageHeight,
        imageWidth
      }
    }
    
    takeBlobPhoto() {
      const {imageHeight, imageWidth} = this._drawImage()
      this.canvasElement.style.display="block"
      const card = document.createElement('div')
      card.classList.add('card')
      document.querySelector('.wrapper').appendChild(card)
      card.appendChild(this.canvasElement)
      return new Promise((resolve, reject) => {
        this.canvasElement.toBlob((blob) => {
          resolve({blob, imageHeight, imageWidth})
        })
      })
    }
    
    takeBase64Photo({type, quality} = {type: 'png', quality: 1}) {
      const {imageHeight, imageWidth} = this._drawImage()

      const base64 = this.canvasElement.toDataURL('image/' + type, quality)
      
     this.canvasElement.style.display="block" 
      const card = document.createElement('div')
      card.classList.add('card')
      document.querySelector('.wrapper').appendChild(card)
      card.appendChild(this.canvasElement)
      return {base64, imageHeight, imageWidth}
    }
    
  }
  

  customElements.define('simple-camera', SimpleCamera)

</script>
<div class="wrapper">
	<div class="card">
    <simple-camera></simple-camera>

    <div class="active">
      <button id="btnBlobPhoto">Take Blob</button>
      <button id="btnBase64Photo">Take Base64</button>
    </div>
  </div>
</div>

<script>
	(async function() {
		const camera = document.querySelector('simple-camera')
		const btnBlobPhoto = document.querySelector('#btnBlobPhoto')
		const btnBase64Photo = document.querySelector('#btnBase64Photo')

		await camera.open({video: {facingMode: 'user'}})

		btnBlobPhoto.addEventListener('click', async event => {
			const photo = await camera.takeBlobPhoto()
		})

		btnBase64Photo.addEventListener('click', async event => {
			const photo = camera.takeBase64Photo({type: 'jpeg', quality: 0.8})
		})
	}())
</script>
* {
	box-sizing: border-box;
  margin: 0;
  padding: 0;
}
:root {
 --cardWidth: 640px;
 --cardHeight: 480px;
 --color: #a972cb; 
 --hover: #ef6eae;
}

body {
  width: 100vw;
  height: 100vh;
	background-color: #fee1e1;
  display: flex;
  justify-content: center;
  align-items: center;
}
.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
}

.card {
	width: var(--cardWidth);
	height: var(--cardHeight);
	border-radius: 5px;
	box-shadow: -20px 30px 116px 0 rgba(92, 15, 15, 0.54);
	overflow: hidden;
	z-index: 4;
  position: relative;
  margin: 5px;
  
  &:nth-child(1):before {
    content: "Video";
    position: absolute;
    top: 0;
    left: 0;
    background: rgba(0,0,0,.7);
    color: #fff;
    padding: 10px;
  }
  &:nth-child(2):before {
    content: "Photo";
    position: absolute;
    top: 0;
    left: 0;
    background: rgba(0,0,0,.7);
    color: #fff;
    padding: 10px;
  }
}

.active {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  background: rgba(0,0,0,.5);
  padding: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
}


button {  
  color: var(--color);
  transition: 0.25s;
  border: 2px solid;
  font: inherit;
  line-height: 1;
  margin: 0.5em;
  padding: 1em 2em;
  background: none;
  cursor: pointer;
  
  &:hover,
  &:focus { 
    box-shadow: inset 0 0 0 2em var(--hover);
    border-color: var(--hover);
    color: #fff;
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.