PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

ブラウザ上で HTML 要素を画像化してテロップ画像を作る

24卒で新卒入社いたしました。メディアサプライチェーン技術部開発第1グループの山口です。
私は業務の中で、動画に任意のテキストをテロップとして重ねられる機能を実装しました。
その際、テロップ画像をサーバーサイドで生成すると、ブラウザでプレビューを表示するまでに若干の遅延が発生する点や、サーバーの稼働コストがかかる点を考慮し、今回は、HTML 要素を画像化するという方法でブラウザ上でテロップの画像データを作成しました。

本記事では、この方法についてご紹介いたします。

HTML要素のテロップを画像化

以下のようなHTML要素を

HTML

<div class="container">
  <div class="telop">
    <p>テロップを</p>
    <p>作成します</p>
  </div>
</div>

CSS

.telop {
  text-align: center
}

以下のようなPNG形式のデータURLに変換することを目指します。

JavaScript

const image_data_url = "data:image/png;base64.......,"

HTMLを画像化する方法として、今回はSVGを利用します。

その理由は、SVGの<foreignObject> を活用することで、HTML要素を直接SVG内に埋め込み
HTMLのレイアウトを維持したまま、画像化することが可能だからです。

1. SVG形式の画像を作成

SVGやCanvasは、<img> のように静的な画像を表示するのではなく、動的に描画・編集・加工できる要素です。
SVGはベクター形式で要素を直接扱いCanvasはピクセル単位で描画するという違いがあります。
どちらもHTML要素を直接埋め込むことはできませんが、SVGHTMLは、どちらも要素(タグ)ベースで構成されるマークアップ言語であり、階層構造を持つ点では共通しています。
この共通点から、SVGとHTMLは相互に組み合わせられそうに思えますが、実際にはHTML要素をそのままSVGの中に埋め込むことはできません。なぜなら、通常、HTML要素をSVGに埋め込む際には、SVG専用のタグ(例えば、<text><path>)に変換する必要があるからです。

しかし、SVGには <foreignObject> という特別な要素があり、
<foreignObject>を使うことでSVG内にHTML要素を直接埋め込むことができるのです。

例えば、以下のように <foreignObject> を使うと、SVG内にHTMLの <div> を配置できます。

<svg>
  <foreignObject>
    <div>
      <p>埋め込んだHTML要素</p>
    </div>
  </foreignObject>
</svg>

ただし、この際に注意が必要です。 上記の状態では、ブラウザが正しく描画しないことがあります。
その理由は、SVGとHTMLがそれぞれ異なるXML名前空間を持っているためです。

名前空間とは、「この要素はどの種類のものか?」を明確にし、異なる要素や属性が衝突しないようにするための識別方法です。
違う名前空間の要素を埋め込む時は、明示的に名前空間を指定しなければ正しく動作しません。

HTMLの中にSVGを埋め込むには、SVG要素に名前空間xmlns="http://www.w3.org/2000/svgを指定し、逆にSVGの中にHTMLを埋め込むには、HTMLに名前空間xmlns="http://www.w3.org/1999/xhtmlを指定する必要があります。

以下のように名前空間を指定すれば、正しく動作します。

HTML

<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200">
  <foreignObject width="100%" height="100%">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <p>埋め込んだHTML要素</p>
    </div>
  </foreignObject>
</svg>

では、SVGの中にHTMLを埋め込む方法が分かったところで、
実際にSVG内部にHTMLを埋め込んだ文字列を作成していきます。

new XMLSerializer().serializeToStringを用いると、HTML要素に名前空間を指定した上で文字列に変換してくれます。

JavaScript

// HTML要素を文字列に変換
function createHtmlText(element) {
  const htmlText = new XMLSerializer().serializeToString(element);
  return htmlText;
}

次に、単に foreignObject に挿入するだけではフォントが正しく適用されないため、フォントを明示的に埋め込む必要があります。
以下のコードでは、@font-face を用いてフォントデータをstyle タグ内に埋め込んでいます。

JavaScript

// フォント情報をstyleタグに格納した文字列取得
function createFontStyleText(fontData) {
  const { fontFamily, fontDataUrl, fontWeight, fontStyle } = fontData;
  const fontStyleText = `
    <style>
      @font-face {
        font-family: ${fontFamily};
        src: url(${fontDataURL}) format('truetype');
        font-weight: ${fontWeight};
        font-style: ${fontStyle};
      }
      .telop {
        font-family: ${fontFamily};
        font-weight: ${fontWeight};
        font-style: ${fontStyle};
      }
  </style>
  `;
  return fontStyleText;
} 

以上を組み合わせて、SVG内部にHTMLを埋め込んだ文字列を作成します。

JavaScript

// svg内部にHTMLを埋め込んだ文字列を作成
function elementToSvgText(element, option) {
  // テキストボックスのサイズを取得
  const { width, height, fontData } = option;

  // HTML要素を文字列に変換
  const htmlText = createHtmlText(element);
  // フォント情報をstyleタグに格納した文字列取得
  const fontStyleText = createFontStyleText(fontData);

  //SvgTextを取得
  const svgText = `
    <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
      <foreignObject width="100%" height="100%">
        ${fontStyleText}
        ${htmlText}
      </foreignObject>
    </svg>
  `;
  return svgText;
}

これでSVG形式のHTMLの画像を取得することができました。

2. SVG形式の画像をPNG形式の画像に変換

一般的に、SVG形式の画像を直接PNG形式の画像に変換することはできません。
これは、SVGがベクター形式であるため、直接的にピクセルデータを持っていないからです。
SVG形式の画像からPNG形式の画像に変換するには、まずSVGをレンダリングして画像データに変換する必要があります。

まず、SVGから<img>タグに読み込ませるためのデータURLを作成します。
encodeURIComponentを使用してSVGテキストをURLエンコードし、その後、エンコードされたテキストを基にdata:image/svg+xml;charset=utf-8の形式でデータURLを生成します。

JavaScript

// SVGをURLエンコードし、SVGデータURLを取得
function createSvgDataUrl(svgText) {
  // SVGテキストをURLエンコードする
  const encodedSvgText = encodeURIComponent(svgText); 

  // データURLを作成する
  const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodedSvgText}`;
  
  return svgDataUrl;
}

次に、データURLを<img>タグに読み込ませ、その<img>要素を<canvas>に描画します。

JavaScript

// SVG画像をCanvasに作画
function drawSvgImageOnCanvas(svgText, option) {
  return new Promise((resolve, reject) => {
    const { x, y, width, height } = option;
    // SVGデータURLを取得
    const svgDataUrl = createSvgDataUrl(svgText);

    const img = new Image();
    img.src = svgDataUrl;

    // 画像が読み込まれたらCanvasに描画
    img.onload = () => {
      // Canvasを作成
      const canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext("2d");

      // Canvasに画像を描画
      ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
      resolve(canvas);
    }

    img.onerror = (error) => {
      reject(new Error("画像の読み込みに失敗しました: " + error.message));
    };
  });
}

<canvas>に描画できたら、toDataURL("image/png")を使って、PNG形式のデータURLを取得します。

JavaScript

 // PNGのデータURLを取得
function getPngDataUrl(canvas) {
  const pngDataUrl = canvas.toDataURL("image/png");
  return pngDataUrl;
};

以上の処理をまとめて、以下の関数にします。

JavaScript

// HTML要素をCanvasに描画
async function drawElementOnCanvas(element, option) {
  if (!option) {
    option = {
      width: element.clientWidth,
      height: element.clientHeight,
      x: 0,
      y: 0,
    };
  }

  // HTML要素をsvgTextに変換
  const svgText = elementToSvgText(element, option);

  // svgTextをCanvasに描画
  const canvas = await drawSvgImageOnCanvas(svgText);

  return canvas;
}

JavaScript

// HTML要素をPNG形式のデータURLに変換
async function elementToPngDataUrl(element, option) {

 // HTML要素をCanvasに描画
 const canvas = await drawElementOnCanvas(element, option);

 // CanvasからPNG形式のデータURLを取得
 const image_data_url = getPngDataUrl(canvas);
 return image_data_url;
}

以上によりHTML要素のテロップを画像化し、PNG形式のデータURLを取得する処理を実装できました。 実際に使用する場合は、以下のように使用します。

JavaScript

// 画像化したいHTML要素を取得
const container = document.querySelector('.container');
const image_data_url = await elementToPngDataUrl(container, { fontData });

今までの流れを図にまとめると以下になります。

HTML要素からPNGデータURLを取得する流れ

以下のような関数を用いて、取得したPNG形式のデータURLを元に、画像をダウンロードすることもできます。

JavaScript

// PNG形式のデータURLをダウンロード
function saveImage(imgDataUrl, file_name){
  const link = document.createElement("a");
  link.href = imgData;
  link.download = file_name;
  link.click();
};

saveImage(image_data_url, 'telop_image_1.png');

実際に動かしてみた

1. テキストボックスからテロップ画像の作成

テキストボックスを画像化し、生成されたテロップ画像をCanvasに描画して確認します。
以下がその結果です。

テロップ画像

テキストボックスの入力内容を含むHTML要素が、そのまま画像として正しく変換されていることを確認できます。

2. 映画ティザーテロップ画像の作成

次に、より実際の使用シーンに近い形で確認してみます。
具体的には、映画のタイトルやキャスト情報、公開日を入力し、それらを反映した映画ティザーのテロップ画像を作成します。 今回はReactを使って、以下のように実装します。

JSX

const MovieTeaser = ({ elementToPngDataUrl }) => {
  const [title, setTitle] = useState("映画タイトル");
  const [cast, setCast] = useState("主演: 山田太郎");
  const [releaseDate, setReleaseDate] = useState("公開日: 2025年夏");

  const teaserRef = useRef(null);

  // 画像データURLに変換する関数
  const handleExport = () => {
    if (teaserRef.current) {
      elementToPngDataUrl(teaserRef.current);
    }
  };

  return (
    <div>
      <div style={{ padding: "10px" }}>
        <label>映画タイトル:</label>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          style={{ marginLeft: "10px", padding: "5px" }}
        />
      </div>
      <div style={{ padding: "10px" }}>
        <label>キャスト名:</label>
        <input
          type="text"
          value={cast}
          onChange={(e) => setCast(e.target.value)}
          style={{ marginLeft: "10px", padding: "5px" }}
        />
      </div>
      <div style={{ padding: "10px" }}>
        <label>公開日:</label>
        <input
          type="text"
          value={releaseDate}
          onChange={(e) => setReleaseDate(e.target.value)}
          style={{ marginLeft: "10px", padding: "5px" }}
        />
      </div>

      {/* テロップの表示部分 */}
      <div style={{ padding: "10px" }}>
        <span>HTML要素のテロップ↓</span>
      </div>
      <div
        ref={teaserRef}
        style={{
          margin: "auto",
          backgroundColor: "#000",
          color: "#fff",
          padding: "20px",
          width: "600px",
          textAlign: "center",
        }}
      >
        <h1>{title}</h1>
        <h2>{cast}</h2>
        <p>{releaseDate}</p>
      </div>

      <button
        onClick={handleExport}
        style={{
          padding: "10px 20px",
          margin: "10px",
          backgroundColor: "white",
          color: "black",
          border: "1px solid black",
          borderRadius: "5px",
          cursor: "pointer",
        }}
      >
        テロップを画像に変換
      </button>
    </div>
  );
};

「テロップを画像に変換」をクリックすると、映画ティザーのテロップ画像を作成できることを確認できます。

映画ティザーのテロップを作成

その他の活用事例

HTML要素を画像化する技術を応用することで、テロップを作成する以外にもさまざまな機能を実装できます。
ここでは、その具体的な活用例をいくつかご紹介します。

1. グラフや表を画像として保存

業務システムやデータ分析ツールでは、グラフをそのまま画像として保存したい場面がよくあります。
以下のようなHTML要素の表を含むダッシュボードを画像に変換します。

HTML

<div id="dashboard" class="dashboard">
  <h2>営業成績の表</h2>
  <table>
    <thead>
      <tr>
        <th>項目</th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>売上</td>
        <td>¥1,000,000</td>
      </tr>
      <tr>
        <td>利益</td>
        <td>¥300,000</td>
      </tr>
      <tr>
        <td>顧客数</td>
        <td>500</td>
      </tr>
    </tbody>
  </table>
</div>

<button class="export-button" onclick="exportDashboard()">ダッシュボードをエクスポート</button>

JavaScript

// ダッシュボードを画像化
async function exportDashboard() {
  const dashboardElement = document.getElementById("dashboard");
  // HTML要素を描画したCanvasを取得
  const canvas = await drawElementOnCanvas(dashboardElement);
  document.body.appendChild(canvas);
}

CSS

.dashboard {
   padding: 16px;
   box-shadow: 0 0 0 1px #ccc;
   background-color: #fff;
   width: 500px;
   height: 300px;
   margin: auto;
   box-sizing: border-box;
}
h2 {
   font-size: 20px;
   font-weight: bold;
   text-align: center;
}
table {
   width: 100%;
   border-collapse: collapse;
   border: 1px solid #ccc;
   margin-top: 16px;
}
th, td {
   border: 1px solid #ccc;
   padding: 8px;
   text-align: center;
}
th {
   background-color: #f0f0f0;
}
.export-button {
   margin: 20px;
   padding: 10px 16px;
   cursor: pointer;
}

「ダッシュボードをエクスポート」をクリックすると、ダッシュボードの画像を作成することができます。
以下がその結果です。

ダッシュボードを画像化

2. 画面のスクリーンショット

HTMLを画像化する方法を活用して、ウェブサイト全体のスクリーンショットを撮影する機能を実装することができます。

await elementToPngDataUrl(document.documentElement) のように、サイト全体を参照するHTML要素に対して、画像変換処理を行います。

以下のような撮影用のデモウェブサイトを用意します。

HTML

<div class="container">
  <!-- ヘッダー -->
  <header>
    <h1>ウェブサイト(仮)</h1>
    <p>スクリーンショット機能付きのデモサイト</p>
  </header>
  <nav>
    <a href="#home">
      ホーム
    </a>
    <a href="#about">
      紹介
    </a>
    <a href="#contact">
      お問い合わせ
    </a>
  </nav>

  <!-- メインコンテンツ -->
  <section class="main-content">
    <h2>メインコンテンツ</h2>
    <p>
      ここでは、コンテンツをお見せしています。スクリーンショット機能を使って、このセクションを保存できます。
    </p>
    <img
      src="https://via.placeholder.com/800x400"
      alt="サンプル画像"
    />
  </section>

  <!-- フッター -->
  <footer>
    <p>© 2025 デモウェブサイト</p>
  </footer>
</div>

CSS

/* 全体のレイアウト */
.container {
  margin: auto;
  width: 100%;
}

/* ヘッダー */
header {
  background-color: #333;
  color: #fff;
  padding: 20px 0;
  width: 100%;
  text-align: center;
}

/* ナビゲーションバー */
nav {
  background-color: #4CAF50;
  padding: 10px 0;
  text-align: center;
}

nav a {
  color: white;
  margin: 0 15px;
  text-decoration: none;
}

/* メインコンテンツ */
.main-content {
  padding: 20px;
  background-color: #f4f4f4;
  text-align: center;
  margin: 20px 0;
  border-radius: 10px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.main-content img {
  width: 100%;
  border-radius: 8px;
}

/* フッター */
footer {
  background-color: #333;
  color: #fff;
  padding: 10px 0;
  text-align: center;
  margin-top: 20px;
}

以下がウェブサイト全体のHTML要素から作成したスクリーンショット画像です。

ウェブサイト全体のスクリーンショット画像

HTML要素が画像化されていることが確認できます。

ただし、<img src="..." />で表示している画像は正しく表示されていません。
HTML内に画像などの外部リソースが含まれている場合、フォントデータと同様に、SVG化する際にはそれらを明示的に埋め込む必要があります。具体的な埋め込み方法については、今回は割愛いたします。

終わりに

以上が、HTMLを画像化する方法とその活用についてのご紹介でした。
この技術は、クライアント側でテロップを効率的に作成し、画像データとして保存するための有力な手段となります。 ぜひ皆様も試してみてください。