Javascript

HTML+CSSで、スマホ縦長カメラを実装する

2022年1月2日

スマホで撮影して画像を保存するというカメラを作ってみようと思います!

今回の実装の内容はこちら!

【実現すること】
・スマホに縦長でカメラを表示する
・シャッターボタンを押すと、
撮影した画像が表示される
・ダウンロードボタンをクリックすると、
画像が保存できる!

【完成イメージ】

カメラの許可→撮影
カメラが止まり、左下にダウンロードボタン
ダウンロードされる

※HTMLとCSS、Javascriptの基本知識があることが前提の記事となっております。

HTML5カメラについては、多くの記事があり参考にさせていただきました。

(ありがとうございます!)

しかし、

スマホ縦長カメラの実装をする方法の記事は少なく、やや苦戦したため、

記録に残しておくことにしたいと思います。

とりま実践!!




下地をHTML+CSSで作る

まずは、様々なサイズのあるスマホ画面に対して、

幅100%/高さ100%でしっかり表示されるように下地を作っていきます。
スマホといっても、今回はタブレットサイズまでは対応できるようにしたいと思います♪

用意するファイルは、index.htmlとstyle.cssのみです!

【HTML】

<!DOCTYPE html>

<html>
    <head>
        <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>縦長カメラのテスト</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="view"></div>
    </body>
</html>

【CSS】

/*Base*/
body{
    margin: 0;
}
.view{
    width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
  min-height: 568px; /*最低縦幅*/
	margin-right: auto;
	margin-left: auto;
    background-color: green;/*背景を緑にしてみる*/
    overflow: hidden;
}

画面で表示してみると、こんな感じになります。

この緑色の部分にカメラ画面を重ねる形になりますので、

しっかり画面サイズに合っているか確認してくださいね。

スマホ表示

補足

まずindex.htmlとstyle.cssを作成します。

■index.html

style.cssの読み込みを行います。(7行目)

<div>要素を用意します(10行目)

■style.css

max-width: 1024px; とmin-height: 568px; は、

タブレット端末に対応させることを基準に割り出しています。

overflow: hidden;は、

今回全画面スマホカメラを実装するため、スクロールをさせないために記述しています。

画面からはみ出た部分は、これで表示されなくなります。

(一部対応しない場合があるらしいが、今回は問題なし)

     

カメラを実装していく

先ほどの緑色の部分にカメラを実装していきます。

<!DOCTYPE html>

<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>縦長カメラのテスト</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="view">
            <!--以下2行追加!-->
            <video id="Video" autoplay="1" playsinline ></video>
            <button type="button" onclick="shutter()" id="shutter">シャッター</button> 
        </div>
        
        <!--以下追加!-->
        <!--jqueryも読み込んでおきます-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <!--script-->
        <script type="text/javascript">
            function videoStart(){
                var constraints = { audio: false, video: { facingMode: "environment" } };
                navigator.mediaDevices.getUserMedia( constraints )
                .then(
                    function( stream ) {
                        var video = document.querySelector( 'video' );
                        video.srcObject = stream;
                        video.onloadedmetadata = function( e ) {
                            video.play();
                        };
                    }
                    )
                };
                videoStart();
        </script>
    </body>
</html>
/*Base*/
body{
    margin: 0;
}
.view{
    width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
	min-height: 568px; /*最低縦幅*/
	margin-right: auto;
	margin-left: auto;
    background-color: green;
    overflow: hidden;
}

/*ここから追加*/
/*Camera*/
.view video{
	position: relative;
	object-fit: fill;
    width: 100%;
    height: 100vh;
	max-width: 1024px;
	margin-right: auto;
	margin-left: auto;
}
.view #shutter{
	position:absolute;
	bottom:10%;
	left:50%;
	z-index: 5555;
}
許可画面が表示される
OKを押すと、カメラが表示される
           

シャッターボタンちっさ!!

           

このあとで修正していきましょう、、笑

ちなみに現段階では、

「シャッターボタンを押した後」の処理を記述していませんので、何も反応しません。

補足

■index.html

videoタグを使って、撮影画面を表示します。

playsinline属性は、iphone(ios)で表示するために必要です(12行目)

buttonタグを使ってシャッターボタンを実装します。

クリックすると、javascriptのshutter関数が実行されます。※shutter関数はこのあと作ります!(13行目)

<script ~>からjavascriptのコードとなります(20行目)

{ audio: false, video: { facingMode: "environment" } }の、

facingMode: "environment" はフロントカメラの使用を指定しています。

facingMode: "user"にするとリアカメラ(自撮りカメラ)になります。 audioはfalseにしています。(23行目)

.then(function(stream) { ~で成功時の処理を記述しています。(24行目~)

■style.css

videoタグをposition: relative;、シャッターボタンをposition:absolute;にして重ねています。

(19行目、28行目)

object-fit: fill;は縦横比についての記述です。

fillは縦横比を無視します。

この記述がないと、videoタグは縦横比をキープしようとして、画面全体になりません。 (20行目)

getUserMedia()について
カメラ画面は、プライバシーの観点から、
localhostまたはHTTPSからロードされたページでないと表示されません。

getUserMedia() is a powerful feature which can only be used in secure contexts; in insecure contexts, navigator.mediaDevices is undefined, preventing access to getUserMedia(). A secure context is, in short, a page loaded using HTTPS or the file:/// URL scheme, or a page loaded from localhost.

developer.mozilla.org
     

画像を撮影し、保存出来るようにする

画面に表示された映像を、撮影し、保存するまでを行いたいと思います。

現在、画面に表示されているのは映像です。

そのため、以下のステップが必要になります。

①canvas要素を用意する
②canvas要素に映像の1コマを表示させる
③canvas要素を画像ファイル(jpegとかpng)に変換して、保存する

     

「あー、大変そう!!泣」

     

ってなりますが、意外と単純なプログラムです。

(諦めて、戻るボタン押さないで(笑))

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>縦長カメラのテスト</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="view">
            <video id="Video" autoplay="1" playsinline ></video>
            <button type="button" onclick="shutter()" id="shutter">シャッター</button> 

            <!--2行追加-->
            <canvas id="capture"></canvas>
            <a id="download" href="#" download="canvas.jpg">ダウンロード</a>
        </div>
        
        <!--jquery-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <!--script-->
        <script type="text/javascript">
            function videoStart(){
                var constraints = { audio: false, video: { facingMode: "environment" } };
                navigator.mediaDevices.getUserMedia( constraints )
                .then(
                    function( stream ) {
                        var video = document.querySelector( 'video' );
                        video.srcObject = stream;
                        video.onloadedmetadata = function( e ) {
                            video.play();
                        };
                    }
                    )
                };
              videoStart();

              //以下を追加
            function shutter(){
                var canvas_capture = document.getElementById('capture');
                var cci = canvas_capture.getContext('2d');
                var va = document.getElementById('Video');

                $('#capture').css('visibility','visible');//画面一番上にcanvasを表示
                $('#download').css('visibility','visible');//さらにダウンロードボタンも表示
                va.pause();

                canvas_capture.width = va.videoWidth;
                canvas_capture.height = va.videoHeight;
                cci.drawImage(va,0,0); //canvasに描写
                
                // canvasを画像で保存
                $("#download").click(function(){
                    var base64 = canvas_capture.toDataURL("image/jpeg");
                    document.getElementById("download").href = base64;
                    });
            }

        </script>
    </body>
</html>

CSSでは、ちゃっかり小さかったシャッターボタンのサイズと位置を調整しています(笑)

/*Base*/
body{
    margin: 0;
}
.view{
    width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
	min-height: 568px; /*最低縦幅*/
	margin-right: auto;
	margin-left: auto;
    background-color: green;
    overflow: hidden;
}

/*Camera*/
.view video{
	position: relative;
	object-fit: fill;
    width: 100%;
    height: 100vh;
	max-width: 1024px;
	margin-right: auto;
	margin-left: auto;
}
.view #shutter{
	position:absolute;
	font-size: 20px; /*追加*/
	padding: 10px;/*追加*/
	bottom:10%;
	left: 50%;/*ついでに追加*/
	transform: translateY(-50%) translateX(-50%);/*ついでに追加*/
	z-index: 5555;
}

/*以下すべて追加*/
/*capture-image*/
.view canvas{
    position: absolute;
	top:0;
	left: 0;
	right: 0;
	width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
	min-height: 568px; /*最低縦幅*/
	margin: auto;
    overflow: hidden;
	z-index: 5556;
	visibility: hidden;
}
.view #download{
	position: absolute;
	font-size: 25px;
	bottom: 1%;
	z-index: 5556;
	visibility: hidden;
}

どんな画面になったか確認してみましょう!

シャッターボタンを押す
撮影画像が表示され、左下にダウンロードボタンが表示される
canvas.jpgという名前で保存される
     

ダウンロードボタンもちっさ!!

シャッターボタン変わらずちっさ!

2022.3.24修正:metaタグのviewportを設定していませんでした。当記事内のコードにはすべてviewportが追記されています。

   

しかし、色々やっていくと修正したいところを発見しました。

☆保存された画像が縦長じゃない!

   

最後に、この課題を解決していきましょう!

あと一息だあ、、、!!

補足

■index.html

$('#capture').css('visibility','visible');
$('#download').css('visibility','visible');
では、シャッターボタンが押されたら、
canvasとダウンロードボタンを非表示から表示に切り替えます。

この2つは、videoタグより前面に来るようにcssで設定しているため、
表示したままだとシャッターボタンが押せなってしまいます。
(41行目、42行目)

va.pause();
では、canvasにより隠れたvideoを停止させます。(動作が重くなるのを防ぎます)
(43行目)


cci.drawImage(va,0,0);
では、drawImageを使ってcanvasに描画しています。
(47行目)


$("#download").click(function(){~
では、ダウンロードボタンが押された後の処理を記述しています。
toDataURLを使って、canvas要素をjpegとして取得しています。

■style.css
z-index:5556
では、videoタグより前面に来るように大きい数値にしています。
(49行目、56行目)

     

canvasを縦長サイズで保存する

まずは現状を再確認しておきます!

ダウンロード画面
ダウンロードされた画像

一見気にならないかもしれませんが、 縦長写真になっていないですね。
何より、画像が一部切り取られて保存されるのは避けたいところです。


この原因はなんなのか…。

検証が画面にて、適用さているCSSを確認してみると、何やら勝手に適用されています。

aspect-ratio: auto 640/480;

保存された画像のサイズを確認してみましょう。


画面には、100%で表示されているのに…。

なんてこった、パンナコッタ…

ちなみにCSSで
aspect-ratio: auto 720/480 !important;
として、上書きしようとしても変わりません。

canvas要素・video要素には、CSSを変更するのではなく、属性で変更しなければなりません。
CSSでは縦横のサイズを変更していますが、
これでは見た目上の領域が拡大しているだけで、実際の内部の描画領域は拡大していません。

今回、

canvas_capture.width = va.videoWidth;
canvas_capture.height = va.videoHeight;

にて、canvasの縦横 =videoタグの縦横 という指定をしているため、videoタグに理想の縦横サイズを指定していきます。

次の記述を、index.htmlに付け足します。

width: 720,height: 480


※見やすいように一部改行しております。

-----省略------       
 <!--jquery-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <!--script-->
        <script type="text/javascript">
            function videoStart(){
                var constraints = { 
                   audio: false, 
                   video: { facingMode: "environment",
    <!--widthとheightを追加-->
                       width: 720,height: 480
                    }
                };
                navigator.mediaDevices.getUserMedia( constraints )
                .then(
                    function( stream ) {
                        var video = document.querySelector( 'video' );
                        video.srcObject = stream;
                        video.onloadedmetadata = function( e ) {
                            video.play();
                        };
                    }
                    )
                };
              videoStart();

------以下省略------

スマホで見てみます!

ダウンロード画面
保存した画像

切れているところもほとんどなく、いい感じですね!!

ダウンロード画面に表示されている画像が、縦に引き伸ばされているのは、
スマホ画面が先ほどしていした720x480でなく、
無理やりcssで100%に合わせているからだと考えられます。

このあたりは、ご自身のスマホサイズで様子を見ながら調整してみてください。

今回、width:720 height:480で指定していますが、

縦長の画像を作るので、本来逆では?? と思うのですが、
数値を逆にすると横長になってしまいます。

原因は分かりませんが、端末によって異なるのかもしれません。

スマホでは縦長になり、PCで横長画像(横720x高さ480)で保存されました。
どちらも対応させたい場合には、
シャッターボタンを押した後の処理で、widthとheigthを変える必要があるかもしれませんね。

原因わかる方、教えてくださいー---(´;ω;`)

補足

アスペクト比とは…
画面や画像の縦と横の長さ(画素数)の比のことです。
上記の場合は、縦640x横480ということになり、4:3ということになります。
html/cssで動画などを扱う際には、注意が必要です。

     

完成

<!DOCTYPE html>

<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>縦長カメラのテスト</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="view">
            <video id="Video" autoplay="1" playsinline ></video>
            <button type="button" onclick="shutter()" id="shutter">シャッター</button> 

            <!--追加-->
            <canvas id="capture"></canvas>
            <a id="download" href="#" download="canvas.jpg">ダウンロード</a>
        </div>
        
        <!--jquery-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <!--script-->
        <script type="text/javascript">
            function videoStart(){
                var constraints = { audio: false, video: { facingMode: "environment", width: 720,height: 480 } };
                navigator.mediaDevices.getUserMedia( constraints )
                .then(
                    function( stream ) {
                        var video = document.querySelector( 'video' );
                        video.srcObject = stream;
                        video.onloadedmetadata = function( e ) {
                            video.play();
                        };
                    }
                    )
                };
              videoStart();

              //以下追加
            function shutter(){
                var canvas_capture = document.getElementById('capture');
                var cci = canvas_capture.getContext('2d');
                var va = document.getElementById('Video');

                $('#capture').css('visibility','visible');//画面一番上にcanvasを表示
                $('#download').css('visibility','visible');//さらにダウンロードボタンも表示
                va.pause();

                canvas_capture.width = va.videoWidth;
                canvas_capture.height = va.videoHeight;
                console.log(va.videoHeight+':'+va.videoWidth)
                cci.drawImage(va,0,0); //canvasに描写
    
                
                 //canvasを画像で保存
                $("#download").click(function(){
                    var base64 = canvas_capture.toDataURL("image/jpeg");
                    document.getElementById("download").href = base64;
                    });
            }

        </script>
    </body>
</html>
/*Base*/
body{
    margin: 0;
}
.view{
    width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
	min-height: 568px; /*最低縦幅*/
	margin-right: auto;
	margin-left: auto;
    background-color: green;
    overflow: hidden;
}

/*Camera*/
.view video{
	position: relative;
	object-fit: fill;
    width: 100%;
    height: 100vh;
	max-width: 1024px;
	margin-right: auto;
	margin-left: auto;
}
.view #shutter{
	position:absolute;
	font-size: 20px; /*追加*/
	padding: 10px;/*追加*/
	bottom:10%;
	left: 50%;/*ついでに追加*/
	transform: translateY(-50%) translateX(-50%);/*ついでに追加*/
	z-index: 5555;
}

/*capture-image*/
.view canvas{
    position: absolute;
	top:0;
	left: 0;
	right: 0;
	width: 100%;
    height: 100vh;
    max-width: 1024px; /*最高横幅*/
	min-height: 568px; /*最低縦幅*/
	margin: auto;
    overflow: hidden;
	z-index: 5556;
	visibility: hidden;
}
.view #download{
	position: absolute;
	font-size: 25px;
	bottom: 1%;
	z-index: 5556;
	visibility: hidden;
}

気になるところも残りますが、一旦、これで完成としたいと思います!!

今後、追記事項がありましたら加えていきます!

では、また☆

相談・質問を募集しています。

記事に関する相談・質問を募集しています。

是非この機会にどうぞ
コメントでの質問も大歓迎です!

ハヤぶろぐのLINE@登録はこちら


メールの方はこちら




-Javascript