<video>に字幕をつける
動画に字幕を入れる方法について、.vtt ファイルを用いる方法と、Javascriptを使って自力で要素をオーバーレイする方法の2つのサンプルを示します。
余談ですが、<video>動画を自動再生(autoplay)させるには、ミュート(muted)属性を付ける必要があります。
vttで字幕表示
.vttファイルを使うと字幕表示できます。
.vttファイルは以下のような字幕文字と時間を記述したテキストです。
WEBVTT 00:00.000 --> 00:02.000 開始しました。 00:02.000 --> 00:05.000 飛んでいます。 00:05.000 --> 00:08.000 操縦が下手なのでフラフラしています。 00:09.000 --> 00:14.000 ぶつからないか、墜落しないか心配です。 00:15.000 --> 00:18.000 着陸しました。
<track>タグで vtt ファイルを指定すれば字幕が表示されます。
疑似要素 ::cue を使って字幕の色や文字サイズを変えることが出来ます。
<video id="vtt" src="./imgs/video.mp4" muted autoplay controls playsinline loop controlslist="nofullscreen" style="width:320px;height:auto;" width="1280" height="780"> <track default kind="captions" srclang="ja" label="label" src="./imgs/video.vtt" /> </video> <style> video#vtt::cue{ font-size:20px; color:#fff; background:rgba(0,0,0, 0.7); } </style>
Javascriptを使って自力で要素をオーバーレイ(被せる)
Javascriptを使って字幕の要素をビデオにオーバーレイ(ビデオに字幕を被せる)してみます。
<div id="overlay1" style="box-sizing:border-box;position:relative;width:400px;max-width:100%;"> <video src="./imgs/video.mp4" muted controls playsinline loop controlslist="nofullscreen" style="width:100%;height:auto;margin:auto;display:block;position:relative;" width="1280" height="780"> </video> <div style="position:absolute;margin:auto;top:0;pointer-events:none;"> <!-- ここに字幕をオーバーレイさせる --> </div> </div> <script> class TAddTextToVideo{ setOverlay(){ let w="",h=""; if(this.ow>=this.oh){ w="100%"; h="auto"; }else{ if(this.oh>400){ h="400px"; }else{ h=this.oh+"px"; } w="auto"; } this.video.style.width=w; this.video.style.height=h; let style=window.getComputedStyle(this.video); this.overlay.style.width=style.width; this.overlay.style.height=style.height; this.overlay.style.marginLeft=style.marginLeft; for(let i=0;i<this.elms.length;i++){ this.elms[i].style.fontSize = 6+parseInt(style.width)/40+"px"; } } constructor(video, overlay){ this.tables=[ {"min": 0.0, "max": 2.0, "left":"25%", "top":"10%", "text":"開始しました。" ,}, {"min": 2.0, "max": 5.0, "left":"38%", "top":"20%", "text":"飛んでいます。" ,}, {"min": 5.0, "max": 8.0, "left":"10%", "top":"40%", "text":"操縦が下手なのでフラフラしています。" ,}, {"min": 9.0, "max":14.0, "left":" 2%", "top":"60%", "text":"ぶつからないか<br>墜落しないか心配です。" ,}, {"min":15.0, "max":18.0, "left":"30%", "top":"80%", "text":"着陸(墜落)しました。" ,}, ]; this.ow=0; this.oh=0; this.video=video; this.overlay=overlay; this.elms=[]; for(let i=0;i<this.tables.length;i++){ this.elms[i]=document.createElement("p"); this.elms[i].style.display="none"; this.elms[i].style.position="absolute"; this.elms[i].style.left=this.tables[i].left; this.elms[i].style.top=this.tables[i].top; this.elms[i].style.margin=0; this.elms[i].style.padding=0; this.elms[i].style.background="rgba(255,255,255, 0.8)"; this.elms[i].style.color="black"; this.elms[i].style.pointerEvents="auto"; this.elms[i].innerHTML=this.tables[i].text; this.overlay.appendChild(this.elms[i]); } //ウィンドウ リサイズ時 window.addEventListener('resize',function(){ this.setOverlay(); }.bind(this)); //再生時間が更新されているとき this.video.addEventListener('timeupdate',function(){ for(let i=0;i<this.tables.length;i++){ if(this.tables[i].min<=this.video.currentTime && this.tables[i].max>=this.video.currentTime){ this.elms[i].style.display="block"; }else{ this.elms[i].style.display="none"; } } }.bind(this)); //再生の準備が完了したとき this.video.addEventListener('canplay',function(){ this.setOverlay(); this.video.play(); }.bind(this)); //メタ情報の読み込みが完了 this.video.addEventListener('loadedmetadata',function(){ this.ow=this.video.videoWidth; this.oh=this.video.videoHeight; }.bind(this)); } } window.addEventListener("DOMContentLoaded",function(){ addTextToVideo=new TAddTextToVideo( document.querySelector("#overlay1>video"), document.querySelector("#overlay1>div") ); }); </script>
Javascriptを使って自力で要素をオーバーレイ(ビデオに字幕を被せる)するが右から左へ流す
自力で字幕の要素をオーバーレイ(ビデオに被せる)しますが、右から左へ文字を流してみます。
timeupdate は0.25秒~0.5秒間隔しかイベントが飛んでこないので、setIntervalを使用しています。
<div id="overlay2" style="box-sizing:border-box;position:relative;width:400px;max-width:100%;"> <video src="./imgs/video.mp4" muted controls playsinline loop controlslist="nofullscreen" style="width:100%;height:auto;margin:auto;display:block;position:relative;" width="1280" height="780"> </video> <div style="position:absolute;margin:auto;top:0;pointer-events:none;overflow:hidden;"> <!-- ここに字幕をオーバーレイさせる --> </div> </div> <script> class TAddScrollTextToVideo{ setOverlay(){ let w="",h=""; if(this.ow>=this.oh){ w="100%"; h="auto"; }else{ if(this.oh>400){ h="400px"; }else{ h=this.oh+"px"; } w="auto"; } this.video.style.width=w; this.video.style.height=h; let style=window.getComputedStyle(this.video); this.overlay.style.width=style.width; this.overlay.style.height=style.height; this.overlay.style.marginLeft=style.marginLeft; for(let i=0;i<this.elms.length;i++){ this.elms[i].style.fontSize = 6+parseInt(style.width)/40+"px"; this.tables[i].right=parseFloat(style.width); this.tables[i].left = - parseFloat(window.getComputedStyle(this.elms[i]).width); } } constructor(video, overlay){ this.tables=[ {"min": 0.0, "max": 2.0, "top":"10%", "text":"開始しました。" ,}, {"min": 2.0, "max": 5.0, "top":"20%", "text":"飛んでいます。" ,}, {"min": 5.0, "max": 8.0, "top":"40%", "text":"操縦が下手なのでフラフラしています。" ,}, {"min": 9.0, "max":14.0, "top":"60%", "text":"ぶつからないか<br>墜落しないか心配です。" ,}, {"min":15.0, "max":18.0, "top":"80%", "text":"着陸(墜落)しました。" ,}, ]; this.ow=0; this.oh=0; this.video=video; this.overlay=overlay; this.elms=[]; for(let i=0;i<this.tables.length;i++){ this.elms[i]=document.createElement("div"); this.elms[i].style.display="block"; this.elms[i].style.position="absolute"; this.elms[i].style.left="100%"; this.elms[i].style.top=this.tables[i].top; this.elms[i].style.margin=0; this.elms[i].style.padding=0; this.elms[i].style.background="rgba(255,255,255, 0.8)"; this.elms[i].style.color="black"; this.elms[i].style.pointerEvents="auto"; this.elms[i].style.whiteSpace="nowrap"; this.elms[i].innerHTML=this.tables[i].text; this.overlay.appendChild(this.elms[i]); } //ウィンドウ リサイズ時 window.addEventListener('resize',function(){ this.setOverlay(); }.bind(this)); setInterval(this.interval.bind(this),33); //再生の準備が完了したとき this.video.addEventListener('canplay',function(){ this.setOverlay(); this.video.play(); }.bind(this)); //メタ情報の読み込みが完了 this.video.addEventListener('loadedmetadata',function(){ this.ow=this.video.videoWidth; this.oh=this.video.videoHeight; }.bind(this)); } interval(){ for(let i=0;i<this.tables.length;i++){ if(this.tables[i].min<=this.video.currentTime && this.tables[i].max>=this.video.currentTime){ let rate= 1-(this.video.currentTime-this.tables[i].min)/(this.tables[i].max-this.tables[i].min); this.elms[i].style.left = this.tables[i].left + (this.tables[i].right-this.tables[i].left)*rate + 'px'; }else{ //this.elms[i].style.display="none"; this.elms[i].style.left="100%"; } } } } window.addEventListener("DOMContentLoaded",function(){ addScrollTextToVideo=new TAddScrollTextToVideo( document.querySelector("#overlay2>video"), document.querySelector("#overlay2>div") ); }); </script>