スマホのカメラを HTML ページから起動するには、以下のようなタグを用意する方法があります:
<input type="file" accept="image/*" capture="camera"/>


この方法でボタンからカメラを起動すると、カメラで撮影した画像をこのタグの value として引き渡すことができます。「カメラで画像を撮影してアップロード」する方法としては非常に簡単です。

ただこの方法には1つ大きな難点があります。この方法で起動したカメラは HTML による装飾ができず、常にスマホの全画面を使って写真撮影することになります。つまり「撮影中は決まった UI しか使えない」のです。

具体的には以下のようになります(iOS 14 の場合)。<input> タグ自体は CSS で見た目を変えることができるため、例えばこのような画面を用意して、カメラアイコンをタップするとカメラを起動、させるように作ることができます。この「カメラを起動する前」までの UI は自由にデザイン可能です:
2021060801


しかし実際にカメラを起動するとこのような画面になります。全画面でカメラが有効になり、撮影ボタンを押すことで、その瞬間のカメラ映像を記録できます。ただ「撮影ボタンを押す」というアクションを避けることはできません(画面内に QR コードが認識したら自動的に・・・といったことはできません):
2021060802


撮影すると一度この確認画面になり、「写真を使用」を選択することで処理を続けることができるようになります(再撮影した場合は再度撮影画面を経てこの画面になります):
2021060803


カメラ画面を終了すると元の画面に戻ります。ここからはまた自由に見た目を調整することができ、また既に撮影済みの画像のデータを取得することもできるので(File API などと組み合わせることで)プレビュー表示したり、ここから QR コードを読み取って・・・といったこともできるようになります:
2021060804


といった具合です。撮影後は元の画面に戻るので撮影の前後ではデザイン装飾されたページを参照できるのですが、撮影時だけは(撮影し終わるか、キャンセルするまでは)どうにもできません。シャッターを押す、というプロセスを変えることができない UI なので、シャッターを押すことなく画面内に QR コードを確認したら内容を読み取って次の処理へ・・・といったカスタマイズはできないことを意味しています。簡単に実装できる一方で、この撮影前後でのインターフェースにかなり大きな制約を受けることになります。

今回、なんとかして HTML 画面内で上述のようなこと(シャッターを押すことなく画面内に QR コードを確認したら内容を読み取って次の処理)を実現できないかと考え、一応見た目的にはできそうな目処がたったのでサンプルと合わせて紹介します。


まず、サンプルアプリケーションはこちらで試すことができます。ウェブカメラ付きの PC ブラウザか、カメラ付きスマホのブラウザでこちらにアクセスしてみてください(以下の画像は iOS 14 の Safari ブラウザを使った場合の例です):
https://dotnsf.github.io/html_camera_inside/


まず以下のようなダイアログが表示されてカメラへのアクセス許可を求められる(今回の方法だとこの確認ダイアログを回避することはできないと思います)ので「許可」してください:
2021060805


(詳しくは後述しますが)PC ブラウザの場合はウェブカメラ(つまりフロントカメラ)、スマホの場合は背面カメラが有効になり、カメラが映すストリーム映像がウェブ画面上部に表示され続けます。この画面は HTML および CSS で装飾されているもので、自由にカスタマイズできます(上述の「決まったUIしか使えない」ものとは異なります)。今回の例では HTML 画面内に背面カメラの映像を映し続けるようにしています(この画面は HTML で作られています):
2021060806



実際のアプリケーションではここに「撮影」ボタンなどを用意するなどして、ある瞬間の映像を切り取って画像化したり、その画像をサーバーにアップロードすることも可能です。今回のサンプルではボタンで撮影するわけではなく、映像を常に監視しており、映像内に QR コードが認識されたタイミングでその QR コードデータの内容を表示するようにしています。適当な QR コードを探して、スマホや PC ブラウザの映像に映るようにして、動作を確認してみてください:
2021060807



以上がサンプルアプリケーションの紹介です。以下はソースコードの解説です。


このサンプルアプリケーションのソースコードをこちらで公開しています:
https://github.com/dotnsf/html_camera_inside


実質的なコードは index.html 1つだけです。このコードの中に以下のような記載があります:
  :
  :
  //. video
  video = document.createElement( 'video' );
  video.id = 'video';
  video.width = cameraSize.w;
  video.height = cameraSize.h;
  video.setAttribute( 'autoplay', true ); 
  video.setAttribute( 'muted', '' ); 
  video.setAttribute( 'playsinline', '' );
  document.getElementById( 'videoPreview' ).appendChild( video );

  //. media
  var data = {
    audio: false,
    video: {
      facingMode: 'user', //. front 
      width: { ideal: resolution.w },
      height: { ideal: resolution.h }
    }
  };
  if( isMobile() ){
    data.video.facingMode = 'environment'; //. back
  }
  media = navigator.mediaDevices.getUserMedia( data ).then( function( stream ){
    video.srcObject = stream;
  }).then( function( err ){
  });
  :
  :
<body>
  <!-- video(visible) -->
  <div class="container" id="videoPreview" style="text-align: center;">
    <h4>Video Preview</h4>
  </div>
  :
  :


この部分で「カメラを有効にして <body> 内に <video> 要素を生成して撮影した様子を表示」しています。以下2つに分けて説明します。

まずは青字部分、ここで <video> 要素を動的に生成して、<div id="videoPreview"></div> 内に追加しています。ここまではそんなに難しくないと思ってます。

そして赤字部分です。ここで MediaDevices.getUserMedia() メソッドを使ってカメラを有効にしています。その際に PC ブラウザではフロントカメラを、スマホでは背面カメラを有効にする必要があるのですが、getUserMedia() メソッドの引数となるオブジェクトの video.facingMode 属性を 'user'(フロント)にするか、'environment'(背面)にするかで切り替えています。getUserMedia() メソッドが成功したら、その結果を上述の <video> 属性の srcObject に指定することでカメラで撮影し続ける結果を <video> 要素内に表示することができるようになります。

なお、getUserMedia() メソッドは https でアクセスしている場合のみ利用することができます。したがって同様のアプリケーションを作る場合も https で利用できるサイトにページを設置する必要がある点に注意してください。

これだけでカメラで撮影しつつ、その映像を HTML ページ内に表示する、という所までは実現できます。ただ <video> タグのままだとこの先のデータ取り出しなどが必要になった場合に不便です。そこで <video> タグ内に表示される映像を(コマ送りで)<canvas> に表示するように処理を追加して、<canvas> の画像データを監視したり取り出して処理できるように改良しています(加えて <canvas> 自体は非表示の <div> 内に生成することで画面的には変化がないようにしています):
  :
  :
  //. canvas
  canvas = document.createElement( 'canvas' );
  canvas.id = 'canvas';
  canvas.width = canvasSize.w;
  canvas.height = canvasSize.h;
  document.getElementById( 'canvasPreview' ).appendChild( canvas );

  //. context
  ctx = canvas.getContext( '2d' );

  //. render video stream into canvas
  _canvasUpdate();
  :
  :

//. render video stream into canvas
function _canvasUpdate(){
  //. video to canvas(animation)
  ctx.drawImage( video, 0, 0, canvas.width, canvas.height );

  //. check QR code
  var img = ctx.getImageData( 0, 0, canvas.width, canvas.height );
  var result = jsQR( img.data, img.width, img.height );
  if( result && result.data ){ 
    alert( result.data ); 
  }else{
    requestAnimationFrame( _canvasUpdate );
  }
};

  :
  :

  <!-- canvas(invisible) -->
  <div  style="text-align: center; display: none;" id="canvasPreview" class="container">
    <h4>Canvas Preview</h4>
  </div>


上述の _canvasUpdate() メソッドでは <video> の映像を(コマ送りになるよう)切り出して ctx.drawImage() メソッドで <canvas> に転写しています。転写後に <canvas> から画像データを取り出し、 QR コードライブラリ : jsQR を使ってデコードします。デコードが成功していたら(画面内に QR コードが映っていたと判断して)そのデコード結果を alert() 表示します。デコードが成功しなかった場合は QR コードは映っていなかったことになるので、requestAnimationFrame メソッドを使って再度 _canvasUpdate() メソッドを呼び出します※。これを繰り返すことで <canvas> にも(不自然なコマ送りにならずに)自然な映像を表示することが実現できています。

※<video> と <canvas> を連動させるこの技術は他でも応用できそうな印象です。


なお上述の青字部分の、特に以下の部分(青太字の3行)についてコメントを加えておきます:
  video.id = 'video';
  video.width = cameraSize.w;
  video.height = cameraSize.h;
  video.setAttribute( 'autoplay', true ); 
  video.setAttribute( 'muted', '' ); 
  video.setAttribute( 'playsinline', '' );
  document.getElementById( 'videoPreview' ).appendChild( video );
  :
  :


ここは処理的には video.autoplay = true; という属性が設定されていれば正しく動くはずの部分です。ところが原因はよくわからないのですが、iOS ではこれ以外に muted と playsinline という2つの属性についてもなんらかの設定がされている必要があり、しかもこれらを setAttribute() メソッドを使って設定しないと正しく動かない、という現象が発生している模様です(実際に setAttribute() を使わずに直接設定して動かすと、最初だけ <video> 内に映像は描画されますが、それ以降は映像が更新されなくなってしまいました。そのため <canvas> への映像更新も止まってしまいました)。その対応のため現状のようなコードになっています。詳しくはこちらも参照ください:
Navigator.mediaDevices.getUserMedia not working on iOS 12 Safari



というわけで、色々苦労しつつもなんとか HTML & CSS で装飾可能なカメラ撮影画面をウェブページでも実現することができそうな目処が立ちました。「カメラを起動する」ための便利なタグを使うのではなく、どちらかというと動画撮影の機能を有効にした上で、ある瞬間を切り取って撮影したことにする、というアプローチで実現しています。具体的な UI の実現方法はここで紹介した方法以外にもあると思うので、このサンプルを参考にしつつも色々挑戦してみてください。