【Vue3】Canvas要素に video タグ内の動画を表示する【TypeScript】

みなさん、こんにちは。どんぶラッコです。

今日は、canvas要素にvideoタグの中身を表示させるサンプルについて書いていこうと思います!

やりたいこととしては↓↓こういうことです。

ちょっとわかりづらいですが、画面上側が <canvas> 要素です。そこに 画面下部の <video> 要素で読み込んでいるビデオの内容をcanvasタグに反映させています。

では早速 コンポーネントのサンプルとその中身の解説をしていきます!

コンポーネントのサンプル

<script setup lang="ts">

import {
  ref,
  reactive,
  onMounted,
} from 'vue';

// refする要素の準備
const canvas = ref<HTMLCanvasElement>();
const video = ref<HTMLVideoElement>();

const canvasSize = reactive({
  width: 640,
  height: 480,
});

// 実際に描画をする画面
const drawCanvas = () => {
  if (canvas.value === undefined || video.value === undefined) return;
  // レンダリング対象のcanvas要素コンテキストを取得
  const context:CanvasRenderingContext2D|null = canvas.value.getContext('2d');
  if (context === null) return;
  context.drawImage(
      video.value,
      0, 0,
      canvasSize.width, canvasSize.height,
  );
  requestAnimationFrame(drawCanvas);
};


// Events
onMounted(() => {
  drawCanvas();
});


</script>

<template>
  <div>
    <p>canvas</p>
    <canvas
      ref="canvas"
      :width="canvasSize.width"
      :height="canvasSize.height"
    />
  </div>
  <div>
    <p>video</p>
    <video
      ref="video"
      src="mock/sample2.mp4"
      autoplay
      controls
      width="400"
    />
  </div>
</template>

ではまずテンプレートの部分から。

<template>
  <div>
    <p>canvas</p>
    <canvas
      ref="canvas"
      :width="canvasSize.width"
      :height="canvasSize.height"
    />
  </div>
  <div>
    <p>video</p>
    <video
      ref="video"
      src="mock/sample2.mp4"
      autoplay
      controls
      width="400"
    />
  </div>
</template>

canvas 要素と video 要素をそれぞれ準備しています。そして、それぞれのHTML要素には ref 要素を付与しています。これは Vue.js側でその要素自体を指定するための記述ですね。 idclass と同じような働きです。

そして、JS側です。

// refする要素の準備
const canvas = ref<HTMLCanvasElement>();
const video = ref<HTMLVideoElement>();

const canvasSize = reactive({
  width: 640,
  height: 480,
});

先ほど HTML要素それぞれにref で指定した変数名を宣言します。これらの要素はレンダリング後にDOM要素の実態が紐づきます。なので、現時点では undefined ですね。

そして、canvasのサイズを変数で格納できるようにしています。こちらはオブジェクト型なので reactive で宣言。

そして、drawCanvas() です。

// 実際に描画をする画面
const drawCanvas = () => {
  if (canvas.value === undefined || video.value === undefined) return;
  // レンダリング対象のcanvas要素コンテキストを取得
  const context:CanvasRenderingContext2D|null = canvas.value.getContext('2d');
  if (context === null) return;
  context.drawImage(
      video.value,
      0, 0,
      canvasSize.width, canvasSize.height,
  );
  requestAnimationFrame(drawCanvas);
};

if (canvas.value === undefined || video.value === undefined) return;
// 中略
if (context === null) return;

ここら辺は、TSのリントエラーを防ぐために入れている処理です。canvas, video は undefinedの可能性があるので弾いています。また、context については仕様上 null許容になっているので、nullだった場合は以下の処理を実行しないようにしています。

では、実際のコード画面です。

といっても描画に必要なのはこの3行だけです。

  const context:CanvasRenderingContext2D|null = canvas.value.getContext('2d');
  context.drawImage(
      video.value,
      0, 0,
      canvasSize.width, canvasSize.height,
  );
  requestAnimationFrame(drawCanvas);

getContext(contextName) でどの形式で描画するのかというルールを定義することができます。今回は2d画面でいいので2dと指定します。

そして、 drawImage(要素, X, Y, 幅, 高さ) で画像や動画を描画させることができます。今回描画したいのは video 要素なので video.value として要素を取り出しています。

最後に requestAnimationFrame で、繰り返し実行を定義している、というわけです。

そして、最後に作り上げた drawCanvas() を 実行するのは DOM要素がマウントされた後にしてもらいたいので、onMounted() メソッドの中で呼び出します。

// Events
onMounted(() => {
  drawCanvas();
});

なぜマウント後に呼び出したいかというと、 canvas 要素、そして video 要素はマウント後にDOM要素として ref に入ってくるから、でしたね!


噛み砕くと意外と単純ですよね♪

前回記事にした画面キャプチャ機能と組み合わせると、画面キャプチャした要素に対して文字を合成する…なんてこともできそうですね♪

ぜひ試してみてください!

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA