見出し画像

ズーム機能付きビデオプレイヤーを作成するためのアイデア

こんにちは!
ALH開発エンジニアのY.Nです。

はじめに

webページに動画を埋め込むためのライブラリは有名どころはvideo.js、react-player、plyrから他にも色々ありますが、どれも再生停止、秒送り、再生速度などを操作する機能のみのものがほとんどです。※一般的な使用にはそれで十分だとは思いますが...
任意の箇所を拡大して見たいというのは意外と需要がある気がしますが、意外と動画をズームして見るためのライブラリや方法の紹介等がありませんでしたので自分なりに思いついた方法を解説してみます。

※video.jsは有料ではありますがアドオンでズーム機能を付けられるみたいです。
Videojs - zoom in video - Nuevodevel.com

※今回作成するものは、ほとんどネット記事の組み合わせなので独自性は皆無ですが
職務の関係上ソースコードの公開はせずに、私が参考にした記事の解説に留めます。ご了承ください。

仕様

以下、二つの仕様を満たす動画プレイヤを実装します。

・マウスでポイントしている箇所を中心にホイールでの拡大縮小
・拡大した状態で表示領域をドラッグで操作

デモはこんな感じ↓

操作gif

実装方法の検討

webページに動画を埋め込むにはvideoタグを使用しますが、当然videoタグには拡大縮小の機能はないので何らかの処理を自作して搭載する必要があります。
私が検討した限りでは実装方法は二つほどありました。

1,canvas要素にvideoを描画し、表示倍率や表示領域を適用する
2,CSSプロパティを動的に変化させ表示倍率や表示領域を適用する

今回は1の方法で進めますが、結論からいいますと2の方が処理の重たさ等含めて優秀な方法だと思われます。
私の場合は1の方がイメージがしやすく実装が容易かったので2は捨てました。
CSSの知識に自信がなかったのでcanvasのように一つの要素にできる方が後々レスポンシブ対応する際に便利だと考えたのですがおそらくそんなことはない気がします。

以下でも言及されていますが、ズーム機能のためだけにcanvasを使用するのは大がかりすぎるとのこと。耳が痛い...

上記の記事ではtranslate(), scale()を使っていますが今はmatrix()で全て賄えるのでそちらを使うのがよいかと。
1と2どちらを採用するにしてもマウスの座標位置の取得やホイールでのイベント発火、表示領域の座標計算は同じようになるはずですので興味がある方は是非CSSの方(↓)も挑戦してみてください。

実装方法の解説

では本題です。
canvasはグラフィックやアニメーションを描画するためのHTML要素ですがdrawImage()メソッドを使ってHTMLのImage要素やvideo要素を画像としてキャンバスに張り付けることができます。
drawImage()メソッドは最大9個の引数を取り、要素の切り取り位置や縮尺を指定することができます。
canvas: グラフィックキャンバス要素
context . drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

よって

1,マウスの座標位置の取得、ホイール操作での縮尺値管理、値に応じた切り取り領域の位置、範囲の計算
2,計算した値を引数に渡し、drawImage()メソッドを秒間30~60回走らせる

ことで動画を拡大縮小しているように見せることができそうです。

では上記の手順を具体的に解説してみます。

切り取り領域の位置、範囲、縮尺の計算

まず、drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)メソッドについて仕様を確認します。

■引数(値)の説明
image:描画するイメージ(指定できるのは要素・ 要素・ 要素 のいずれか)
sx:元イメージ使用範囲の矩形のx座標(初期値は0)
sy:元イメージ使用範囲の矩形のy座標(初期値は0)
sw:元イメージ使用範囲の矩形の幅(初期値はイメージ本来の幅)
sh:元イメージ使用範囲の矩形の高さ(初期値はイメージ本来の高さ)
dx:描画イメージ矩形のx座標
dy:描画イメージ矩形のy座標
dw:イメージを描画する幅(初期値はイメージ本来の幅)
dh:イメージを描画する高さ(初期値はイメージ本来の高さ)

context . drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)より引用

つまり今回の場合、video要素をHTMLから取得し、動画の解像度が1920×1080なら1920×1080のcanvas(実際表示する大きさはCSSで指定できる)を生成し
dx,dyは(0,0)、dw, dhは(1920,1080)固定で
倍率1倍なら、drawImage(video, 0, 0, 1920, 1080, 0, 0, 1920, 1080)
中心に向かって倍率を2倍ならdrawImage(video, 480, 270, 960, 540, 0, 0, 1920, 1080)
これで2倍拡大を実現できます。

実際の実装ではマウスポインタの座標を中心にマウスホイールを回した際に差分の計算を行うので
マウスホイール1回:+0.2倍とすると

倍率1倍(初期値)からホイール1回
→ 
sx += canvas上のマウスポインタx座標 * 0.2 / (1 + 1 * 0.2) * ((1 + 1 * 0.2) - 0.2)
shも同様

となります。正直何が何やらですが仮の値を色々入れてみるとイメージつきやすいかと思います。

また拡大した動画のドラッグ移動は
クリックしたマウスの移動差分を保持してその分sx,shから引いてあげれば実現できます。

この座標計算は以下サイトを基にしています。
今回の実装は以下参考にすればほぼ完了です。

上記サイトについていくつか補足説明します。

HTML要素の取得、処理タイミング

基本的な事ですが「document.getElementById」などは画面が描画されてから初めて取得できるのでreact等使用している場合は、componentDidMount、useEffectでマウント後に処理が走るようする必要があります。

切り取り領域の限界値

最後のcodepen70行目などの

zoomLeft = Math.max(0, Math.min(canvas.width - zoomWidth, zoomLeft));

は切り取り領域が元のソースからはみ出ないようにするための記述です。
切り取り領域x座標の左端(zoomLeft)が必ず 0 < canvas.width - zoomWidth の範囲になります。

レスポンシブ対応

canvas.addEventListener('mousemove', mouseMove);

で引数として渡されるeからは

let rect = e.target.getBoundingClientRect();

とすることで

lect.left:ページに対してcavasの左側が位置しているx座標
e.clientX:ページに対するマウスカーソルのx座標が取得できますが

レスポンシブ対応などでキャンバスの表示上の大きさが変わる場合
マウス位置やドラッグ距離を計算している箇所

mouseX = e.clientX - rect.left;

などでは実際の座標と値がずれてしまうため
(生成する際に指定したcanvasサイズ / 表示上のcanvasサイズ)を掛けて縮尺を反映させてあげる必要があります。

canvas.addEventListener('mousewheel' は非推奨

細かいですが、addEventListenerのキーである'mousewheel'は現在非推奨となっているため'wheel'にリファクタした方が無難です。

以上で任意の点を中心に、画像や動画を拡大・縮小する方法についての補足説明終わりです。これで画像なら仕様を満たすことはできました。
rawImageメソッドは画像も動画も同じように画像に切り取ってcanvasに張り付ける仕様なのでdrawImageメソッドの第一引数をgetElementByIdしたvideo要素にすると処理の瞬間の画像として適用されます。あとは動画として見せるためにパラパラ漫画のように処理を連続させるだけです。

drawImage()メソッドを秒間30~60回走らせる

videoタグの動画をcanvasに描写する方法は以下が参考になります。

ズバリ方法は1秒間に60回drawImage()する、です。
対象メソッドを連続で処理し続けるためにはsetInterval()メソッドを使用します。使い方は
setInterval({対象のメソッド}, {処理の間隔})
で処理の間隔にはミリ秒単位で指定できます。例えば30フレームなら30/1000、60フレームなら60/1000となります。
60フレームにしておけば問題ないですが、処理の重さが気になる場合は30フレームに下げると良さそうです。

画面上に二つの動画が...

ここまで来ればもう一息です。
今の状態では素のvideoタグ
と動画を描画しているcanvasタグ
2つの動画が画面に表示されてしまっていると思います。なのでcanvasの方をvideoタグの上に重ねる必要があります。
要素を重ねる方法はCSS「position:relative」と「position:absolute」で画像や文字を重ねる方法等を参考にvideoタグの方をposition: "absolute"にしてzIndexを-1等すれば実現できます。

おわり

以上で、ズーム機能のある動画プレイヤを実装することができたと思います。
よくある機能かと思いきや意外と記事が少なくて苦戦しました...
だいぶごり押し感のある実装でしたが、一つの方法として見てもらえればと思います。

canvasちょこちょこ触って思いましたが、落書き機能のある動画プレイヤとかも面白そうですね、それならわざわざcanvasに描画した意味もあるのか...?とも思いましたが結局レイヤーのように透過させたcanvasをもう一枚上に重ねる必要がありそうなのでやっぱり関係ないですね。

ちなみに

今回は静的なmp.4ファイルを配置しvideoタグに読み込みましたがvideoタグに描写ができればどんな形式でもズーム機能が適用できるのでhls.jsなどを使えば.m3u8ファイルを読み込んでズーム機能付き配信プレイヤーなんていうのも可能です。

では、読んでいただきありがとうございました。





↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓


↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ALHについてはこちら ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓


↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓