お知らせ・ブログ一覧へ戻る

HTMLで作ったアニメーションをMP4動画にする方法|時刻から描く設計で1コマずつ撮る手順と、翻訳バー・非表示要素で崩れる落とし穴


HTMLで作ったアニメーションをMP4動画にする方法|時刻から描く設計で1コマずつ撮る手順と、翻訳バー・非表示要素で崩れる落とし穴

HTMLで作ったアニメーションをMP4動画にする方法|時刻から描く設計で1コマずつ撮る手順と、翻訳バー・非表示要素で崩れる落とし穴

文字が飛び込んできたり、写真がワイプで切り替わったりする短いPR動画は、動画編集ソフトがなくてもHTMLとJavaScriptで作れます。店舗の求人動画やサービス紹介動画を内製したいときの選択肢になります。

ただし、ブラウザで動いているアニメーションを「画面録画」してMP4にすると、カクつきやすくなります。パソコンの負荷でコマが落ち、録るたびに仕上がりが変わるからです。

先に結論を書きます。

  • アニメーションは「時刻を渡すと、その瞬間の画面になる」作りにします。 たとえば seek(3.25) と呼べば3.25秒時点の画面が出る、という関数を1つ用意します。CSSのtransitionやanimation(ブラウザが実時間で勝手に進める仕組み)は使いません。
  • ヘッドレスブラウザ(画面を表示せずに動くブラウザ)で、1/60秒ずつ時刻を進めながら1コマずつ撮影します。
  • 撮った画像をffmpeg(動画変換の定番ツール)でMP4にまとめます。
  • 撮影サイズを毎コマ検査し、最後に0.25秒刻みの一覧画像で通し確認します。 代表的な数コマを見るだけでは見抜けない崩れがあります。

以下の数字は、当社が2026年9月に作った店舗の求人動画・自社ツールの紹介動画など、15〜30秒のモーション動画(文字・図形・写真を動かす動画)での実測です。

画面録画ではなく「コマ撮り」にする理由

方式 仕上がり 作り直し
画面録画 パソコンの負荷でコマが落ちる。録るたびに違う映像になる 毎回最初から録り直し
コマ撮り(本記事の方式) 負荷に関係なく、全コマが設計どおりの絵になる 直した場面の秒数だけ撮り直せる

コマ撮りの核心は「どのコマも、時刻だけで絵が決まる」ことです。60fps(1秒60コマ)で15秒の動画なら900枚。1枚の撮影に0.5秒かかっても、完成した動画は1/60秒ごとの正確な絵がつながったものになります。

実測では、1920×1080・60fps・900枚の動画で、撮影が約262秒、MP4への書き出し(画質優先の設定)が約78秒、完成ファイルは約5.9MBでした。修正が1場面だけなら、その範囲を数十秒で撮り直して差し替えられます。

手順1:アニメーションを「時刻の関数」で書く

最初の設計がすべてを決めます。ページに次のような関数を1つ用意し、全要素の位置・透明度・切り抜き形状を、渡された時刻 t から計算して直接書き込みます。

window.seek = function (t) {
  // 例:1.0〜1.5秒で見出しが下から現れる
  const p = clamp((t - 1.0) / 0.5, 0, 1);
  title.style.opacity = p;
  title.style.transform = `translateY(${(1 - ease(p)) * 40}px)`;
  // 数字のカウンターも t から計算する(前のコマの値を使わない)
  counter.textContent = Math.round(20 * clamp((t - 2) / 1, 0, 1));
};

ポイントは3つです。

  • 前のコマの状態を引き継がない。 「前回の値に少し足す」書き方をすると、途中の時刻から撮り始めたときに絵が変わります。どの時刻から撮っても同じ絵になる作りにしておくと、部分的な撮り直しができます。
  • アニメーションライブラリを使うなら、止めた状態で時刻を指定する。 GSAP(JavaScriptの定番アニメーションライブラリ)なら、タイムラインを一時停止して tl.time(t) で時刻を指定できます。
  • 確認用に実時間再生も付けておく。 URLに ?play を付けたときだけ実時間で seek を呼び続ければ、ブラウザで動きを見ながら調整できます。

音楽があるなら、テンポを先に決める

BGMに合わせて場面を切り替えるなら、映像より先にテンポを決めます。128BPM(1分間に128拍)なら1小節が1.875秒、8小節でちょうど15.00秒です。カットを小節の頭に置き、映像側と音声側で同じ数値(1拍=60÷128秒)を共有すると、カットと効果音がずれません。ナレーションを載せる場合の合わせ方は「AIナレーションを動画のカットに合わせる手順」で詳しく書いています。

あわせて、場面ごとの「最後の動きが終わる時刻」を表にしてから実装するのがおすすめです。場面の中の動きが尺より長いと、いちばん見せたい動き(ボタンを押す瞬間など)が次の場面への切り替えに隠れてしまいます。目安は「次のカットの0.1秒前までに終える」です。

フォントはパソコンに入っているものを使う

文字はWebフォント(ネットから読み込むフォント)より、Macに標準で入っているヒラギノ角ゴシックなどを指定するほうが安定します。読み込み待ちが発生せず、毎回同じ文字の形で撮れるからです。

手順2:ブラウザで1コマずつ撮影する

撮影にはPlaywright(ブラウザをプログラムから操作するツール)を使います。流れは次のとおりです。

  1. 画面サイズ1920×1080でページを開く
  2. seek(i / 60) を呼ぶ(i=コマ番号)
  3. 撮影して f_00001.jpg のように連番で保存する
  4. iを1つ進めて2へ戻る

撮影の命令は、Playwright標準の page.screenshot() より、ブラウザの開発者向け機能(CDP)の Page.captureScreenshot を直接呼ぶほうが安定します。標準の撮影命令は、フォントがすべて読み込み済みなのに「フォントの読み込み待ち」のまま数十枚目で止まることがあるためです。直接呼ぶ方式に替えると止まらなくなり、速度も上がりました。

保存形式はPNGよりJPEG(画質94前後)のほうが速く撮れます。Canvas(JavaScriptで絵を描く領域)だけで描いている動画なら、Canvasから直接画像を取り出す方法が1枚約15ミリ秒と、画面撮影(約50ミリ秒)の3分の1で済みます。

重い処理は撮影前に減らす

撮影時間の大半は「1コマを描く重さ」で決まります。光の粒170個にぼかしの影を毎コマ付けていた場面は1枚2.4秒かかり、900枚で40分の計算でした。光の粒を「あらかじめ1回だけ描いた画像を貼る」方式に替えるとその場面は1枚0.55秒になり、他の場面と合わせた全体の撮影は約4.5分に縮みました。CSSの filter: blur() を外すだけでも1枚0.41秒→0.36秒に下がります。

動きを滑らかに見せたいとき(モーションブラー)

速く動く要素は、1コマにつき時刻を少しずつずらした4〜6枚を撮って平均すると、本物のカメラのような残像(モーションブラー)が付きます。このとき、ずらす時刻は前向きに取ります(t、t+1/8コマ、t+1/4コマ…)。前後対称に取ると、カット直前のコマに次の場面が混ざり、拍に合わせた切り替えのキレが鈍ります。

手順3:ffmpegでMP4にまとめる

連番画像がそろったら、ffmpegで動画にします。

ffmpeg -framerate 60 -i frames/f_%05d.jpg -i bgm.wav \
  -c:v libx264 -preset slow -crf 16 -pix_fmt yuv420p \
  -c:a aac -shortest out.mp4

-pix_fmt yuv420p は、スマホや多くの再生アプリで確実に再生させるための指定です。サイトに埋め込んでスマホで自動再生させる場合の注意点は「サイトの動画がスマホで再生されないとき」にまとめています。

ffmpegは、Homebrew(Mac用のソフト管理ツール)で入れたものが、関連部品の更新で突然起動しなくなることがあります。Pythonの imageio-ffmpeg パッケージに同梱されたffmpegを使うと、こうした影響を受けずに済みます。

手順4:検品は「毎コマのサイズ」と「通しの一覧画像」

1コマの崩れは気づきにくいので、検品を2段にします。

  • 毎コマ:画像サイズを検査し、1920×1080以外なら止める。 あわせて、書き出し前に全画像を一度開いて、壊れたファイルが混ざっていないか確かめます。
  • 最後:0.25秒刻みで縮小したコマを並べた一覧画像(コンタクトシート)を作り、通しで見る。 たとえば写真を斜めに拭き取るワイプで切り抜き形状の向きを逆に定義すると、「一瞬全面に出てから逆方向に消える」動きになります。この種の崩れは代表的な数コマでは見抜けず、通しの一覧画像で見つかります。ただし写真の縦横比の崩れのように縮小表示では気づきにくいものもあるので、写真を並べる場面は原寸でも確認します。

映像が崩れる主な落とし穴と対策

ここからは、コマ撮りで起こりうる崩れと、その予防策です。

起こりうる症状 原因 対策
途中から撮影サイズが縮み、映像が崩れる Playwrightで通常版のChromeを使うと、日本語ページに「翻訳しますか?」のバーが出て撮影範囲が1920×993に縮むことがある。ページ側の高さは1080のままなので気づけない 起動時に翻訳機能を無効化(--disable-features=Translate,TranslateUI)、HTMLに translate="no" と <meta name="google" content="notranslate">、撮影範囲を1920×1080に固定し、サイズを毎コマ検査
別の場面の注釈ラベルが映り込む 親要素を visibility:hidden にしても、子要素に visibility:visible があると子だけ表示される 場面の出し入れは display:none で行う
グラデーション文字が全部透明になる 非表示(display:none)の場面で文字の幅を測ると0になる 測るときだけ親を display:block; visibility:hidden にする。位置は getBoundingClientRect() の差で測る
全場面がまとめて消える 場面の開始・終了時刻を入れる項目名を、文字要素の名前と同じにすると上書きされる 時刻の項目は ts / te など、要素名と被らない名前にする
Canvasから画像を取り出せない パソコン内のファイル(file://)から読んだ画像をCanvasに描くと、安全のため取り出しが禁止される 画像はデータURI(画像を文字列にしたもの)で読み込むか、ローカルのWebサーバー経由で開く
動画素材が撮影に写らない ヘッドレスでは <video> の再生位置を動かしても描画が更新されない 動画素材は事前に連番画像へ分解して差し込むなど、ブラウザの動画再生に頼らない形にする
分割画面のマスに並べた写真が伸びる 画面を分割したマスに縮めるとき、縦横の倍率を別々にすると映像が引き伸ばされる マスは画面と同じ縦横比で作り、余りは余白にする

長い書き出しは「分割・再開できる形」にする

撮影は数分〜十数分かかるので、止まったときの備えが必要です。

  • Macがアイドル状態になると、裏で動かしている撮影が止まったまま戻らないことがあります。 13分進まなかった撮影が、前面で動かし直すと22秒で終わった例があります。
  • 同じパソコンで別の書き出しが重なると、負荷が極端に上がり、当社の例では処理が数倍〜10倍遅くなりました。 長い撮影の前に uptime で負荷を確認します。
  • スクリプト名は案件ごとに固有の名前にします。 render.py のような汎用名だと、別の作業の停止命令(名前で一括停止する操作)に巻き込まれて止まることがあります。並列処理の子プロセスは親を止めても残ることがあるので、止めるときはプロセス番号を確認して個別に止めます。

対策の型は、0.5〜1秒ごとに区切って書き出し、終わった区間には完了印を付けることです。止まっても続きから再開でき、修正した場面の区間だけ撮り直して、最後につなげれば完成します。

縦型・横型の両方が必要なとき

SNS用の縦型(1080×1920)とサイト用の横型(1920×1080)を両方作るなら、HTMLを2つに分けないほうが楽です。横型を基準に作り、縦型では「上書き用のCSSを1枚差し込む」か「縦型フラグで座標だけ切り替える」形にします。タイミングの設定とBGMは共通のまま使えるので、作り直しが半日から1時間程度に縮みました。横型側を壊していないかは、修正前後の画像をピクセル単位で比べると確実です。

この方式が向く動画・向かない動画

向く 向かない
文字・数字・図形・写真を動かすPR動画、求人動画、サービス紹介動画 実写の撮影素材が中心で、カット編集が主な作業になる動画
同じ型で店舗別・季節別の差し替え版を何本も作る動画 1本きりで、修正もほぼ発生しない動画
ホームページと同じ書体・色で統一したい動画 3D表現や実写合成が必要な動画

経営者の立場で外注先を選ぶ場合も、「修正1か所にどれくらい時間がかかるか」を聞くと、この方式かどうかの見当がつきます。時刻で描く作りなら直した場面だけ撮り直せるので、文字の差し替え程度なら出し直しの手間は小さく済みます。

静止画バナーをAIで動画にする方法は「静止画バナーをAIで動画広告にする手順」、HTMLから作った資料の文字切れを数値で見つける方法は「HTMLから作ったPDFで文字が切れて消えるとき」で紹介しています。

まとめ

  • 画面録画ではなく、時刻を渡すとその瞬間の画面になる関数を作り、1コマずつ撮る
  • CSSのtransition・animationは使わず、ライブラリを使うなら止めた状態で時刻を指定する
  • 撮影はPlaywrightで、固まる場合はCDPの撮影命令を直接呼ぶ。JPEG保存と重い描画の削減で速くする
  • ffmpegは yuv420p を指定してMP4に。Homebrew版が動かないときは imageio-ffmpeg 同梱版を使う
  • 検品は「毎コマのサイズ検査」と「0.25秒刻みの通し一覧画像」の2段
  • 翻訳バー・visibilityの継承・非表示中の計測が、気づきにくい崩れの代表。長い書き出しは分割して再開できる形にする

一度この型を作っておくと、文字や写真を差し替えるだけで店舗別・季節別の動画を短時間で量産できます。内製か外注かで迷う場合も、まず15秒の1本を作ってみると判断しやすくなります。

関連記事

2026.10.04

発注の記録を月別に集計する表は元のシートと分けて作る|元データはIMPORTRANGEでつなぎ、月ごとのシートは「対象月」のセル1つで切り替える作り方と、件数・数量の検算

2026.10.04

多店舗の店舗一覧ページは地図とテキストの一覧を両方置く|県→エリア→ピンで選ばせる地図と、HTMLに最初から書いた店舗カードの作り方、公開前に電話・営業時間・予約先を突き合わせる手順

2026.10.03

実績の画面をSNSに載せるときの匿名化|市名だけ伏せても駅名・地名で店は分かる。店名は丸ごと置き換え、文字認識で実名の残りを機械的に照合する手順


お知らせ・ブログ一覧へ戻る