kinetic-typography について
Kinetic Typographyは、文字そのものを映像の主役にする、リズム重視の動画制作スキルです。短いフレーズやキーワードを、ビートに合わせた動きと強いコントラストで構成します。
主な機能
- 01フレーズとキーワードの分割
- 02音声や音楽ビートに合わせた時間設計
- 03スラム、スライド、フリップの文字演出
- 04高コントラストな書体と色の指針
- 05ループする終わり方と映像チェック
活用シーン
- 01リズミカルな見出し動画の制作
- 02ナレーションや歌詞を動く文字に変換
- 03ソーシャル動画のエンドカードを設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ phantomtonyt/ motion-design-skills --skill kinetic-typographyインストール後、Claude Code で/kinetic-typographyを入力して読み込まれたか確認します。
詳細
キネティックタイポグラフィ
文字そのものが映像になる動画を作ります。フレーズが画面に現れ、画面いっぱいに広がり、強く打ちつけられ、リズムに乗って次のフレーズに置き換わります。動き出したポスターのように、大胆で、速く、音楽的な印象にします。
オプション
| オプション | デフォルト | 備考 |
|---|---|---|
| フォーマット | 縦長 1080×1920 | または横長 1920×1080、正方形 1080×1080 |
| 長さ | 10–20 s | ナレーションまたは曲のセクションに合わせます |
| トーン | パンチのある | calm(0.4 sのスライドとフェード、反転は少なめ)、punchy(デフォルト)、chaotic(すべて大文字、強く打ちつける動き、揺れ、フレーズごとに1回おきに反転) |
| リズム | 固定テンポ | またはナレーションの単語のタイミング、音楽のビート |
ブリーフ(計画の前に回答)
- 視聴者に覚えてほしい一文は何ですか?
- スクリプトを1〜4語のフレーズに分け、各フレーズで主役となる単語を1つ指定します。
- エネルギーが最高潮に達するのはどこですか(色が反転するフレーズ)?
- 最後のロックアップは何ですか(タイトル、行動喚起、ハンドル名)?
読み取りシート(ビートリストと一緒に作成)
各ビートについて、視聴者が理解すべきことを1つ(「ロボットは飛べない」「キャッシュにヒットしなかった」など)と、読み取れる状態を保つ時間を書きます。一度に読ませる内容は1つです。表示時間の最低基準:ラベルは少なくとも0.8 s、文は画面に完全に表示されてから単語あたり約0.3 sです。表示時間に収まらない場合は、表示を速めるのではなく単語を減らします。
モーションの原則
- モーションには因果関係を持たせます。画面上のすべての動きには理由(クリック、落下、パケットの到着など)があり、次の動きはそこからつながります。
- 動画全体を通して主な進行方向を1つ選び、維持します。それ以外の方向は意味づけに使います。成功や結論には上方向、「全体像はこちら」にはズームアウトを使います。
- シーンの退場で、次のシーンの登場を準備します。同じ軸、同じ方向、似た速度にします。クロスフェードするのではなく、オブジェクトを1つ、切り替わりの先まで動かします。
- 図、グラフ、テキストに、意味のない埋め草の動き(浮遊、呼吸、揺れ)を加えません。動かす必要がなければ静止させます。意図的なカメラ移動は1つで十分です。
- イージング:登場は滑らかなイーズアウト(
power2.out〜power4.out)、退場はより素早いイーズイン(登場時間の約75%)。登場は最大0.8 s。グループ全体の時間差は最大0.5 s。オーバーシュート(back.out)は小さなポップに限り、弾性やバウンスは使いません。 - 重要な場面では、一度に動かすものは1つだけにします。視線が追えるのは1つだけです。
- 見せ場の直前に、0.3–0.75 sの静止した間を入れます。
スタイルルール(常に適用)
これらがスタイルを形作ります。色や言葉が変わっても、必ず守ってください。
- 文字は画面幅の50–80%を占めるようにします。小さい文字は映像ではなく字幕に見えてしまうためです。
- 画面上のアイデアは一度に1つにします。1〜4語、最大3行です。それ以外は画面から退場させます。
- すべての単語は動きを伴って登場します(強く打ちつける、スライド、伸縮、マスクで表示、反転)。単にフェードインするだけにはしません。
- 変化はリズムに合わせます。0.25–0.7 sごとに新しい単語または動きを入れ、キーワードはビートまたは強勢のある音節に合わせます。
- コントラストを極端にします。ほぼ黒とオフホワイト、またはその逆に加え、キーワードだけにアクセントカラーを1色使います。
- 各フレーズに主役となる単語を1つ設けます。最も大きくする、アクセントカラーにする、またはほかとは異なる動きをさせます。
デフォルト(自由に変更できます)
フォント、色、言葉、タイミング、背景は変更できます。ユーザーからブランドフォントや色の指定があれば、それを優先します。優先順位は、ユーザーのブランドまたは仕様、明示的なリクエスト、次にこれらのデフォルトです。
- 背景
#111111、テキスト#f4f1ea、アクセント#ff4d2e。エネルギーを出すために、フレーズごとに明るい背景に暗い文字、または暗い背景に明るい文字を反転します。 - ディスプレイフォント:太いコンデンス体(Anton、Bebas Neue、Archivo Black)。主役の単語はすべて大文字にします。補助フォント:小さなつなぎの単語には太いサンセリフ体(Inter 800)を使います。
- サイズ:9:16では主役の単語を180–320 px、小さな単語を60–90 pxにします。
セットアップ(一度だけ)
node -v # needs 22+
ffmpeg -version
npx hyperframes init my-video --non-interactive --resolution portrait && cd my-video # landscape for 16:9
既存の空のフォルダー内で npx hyperframes init . --non-interactive --resolution portrait を実行し、生成された index.html を以下のテンプレートに置き換えます。
作成前に決めること
コードを書く前に、次の項目を1行でユーザーに伝えます。
- アスペクト比: 9:16(1080×1920)または16:9(1920×1080)。デフォルトは9:16です。
- 単語とタイミング: 台本を1~4語のフレーズに分割します。ナレーションがある場合は、単語のタイムスタンプから時間を取得します(
npx hyperframes transcribe voice.wav)。ない場合は、各フレーズを0.6~1.4秒にし、ヒーローワードは長めにします。 - リズムの基準: 音声(強勢のある音節に合わせる)、音楽(ビートに合わせる。
npx hyperframes beats music.mp3)、または固定テンポ(例:120 BPM = 0.5秒ごとに1拍)から選びます。 - 各フレーズのヒーローワードと、適用する動きを決めます。
| 必要なもの | HyperFrames + GSAP(デフォルト) | Remotion |
|---|---|---|
| 単語や文字の動き、マスク、ストレッチ | はい | はい |
| 音声またはビートデータに基づくタイミング | はい(ラベル) | はい(フレーム) |
| NodeとFFmpegだけで実行 | はい | はい |
| ライセンス | 無料 | 3人までは無料、それを超えると有料 |
動きの種類
| 動き | 方法(GSAP) | 用途 |
|---|---|---|
| スラム | from {scale: 2.6, opacity: 0}、expo.out、0.22秒に加え、0.1秒間の6 pxの画面シェイク | ヒーローワード |
| スライドスタック | overflow:hiddenマスク内で行をfrom {yPercent: 110}にし、スタッガー0.06秒、power4.outで0.35秒 | リスト、2行のフレーズ |
| ストレッチ | scaleY: 1.6 → 1、transformOrigin: bottom、back.out(3)で0.3秒 | 強調、動詞 |
| レターカスケード | 文字をfrom {y: -120, rotation: -20, opacity: 0}にし、スタッガー0.025秒、back.out(2) | 名前、タイトル |
| フリップ切り替え | 古い単語をrotationX: 90で退出(0.12秒)、新しい単語をrotationX: -90 → 0で登場(0.18秒) | XをYに変える |
| 分割 | 単語を左右に分けてx: ±140にスライドさせ、間から小さな単語を見せる | 対比 |
| 退出 | yPercent: -110またはscale: 0.7, opacity: 0、0.15秒、power2.in | 退出するすべてのフレーズ |
退出は素早く(0.12~0.18秒)、登場は想像以上にキビキビと(0.18~0.35秒)させます。各フレーズは表示後、少なくとも0.35秒そのままにして、読めるようにします。
目を引く映像にする
- 画面いっぱいに配置する。 フォントサイズではなく、画面幅に合わせて単語のサイズを決めます。250 px以上のコンデンスフォントが効果的です。
- 色の切り替え: 重要な2~3個のフレーズで、ビートに合わせて背景を暗色から明色(またはアクセントカラー)にハードカットで切り替えます。
- テクスチャと奥行き: 控えめなグレインのオーバーレイ、柔らかなシャドウ、ヒーローワードの背後にずらして配置した複製(アクセントカラー、10~14 pxのオフセット)、背景にときどき巨大で薄く表示する単語を加えます。
- カメラの躍動感: ステージ全体をゆっくり2~4%スケールさせ、ヒーローワードの登場時にパンチイン(0.1秒間、スケール1.06)し、スラム時に6~10 px揺らします。
- 単語に図形を添える: 伸びて表示される下線、単語の背後でポップする円、矢印などを加えます。アクセントカラーを使い、フラットな表現にします。
- 最後はロックアップで締める: タイトルまたは行動喚起を完全に構成して1~1.5秒表示し、ループできるよう冒頭のフレームと一致させます。
- 品質チェック: スナップショットのいずれかに、無地の背景の中央に小さなテキストが表示されていたら、まだ完成ではありません。
作成:テンプレート
<!doctype html><html><head><meta charset="UTF-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@800&display=swap">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>
html,body{margin:0;width:1080px;height:1920px;overflow:hidden;background:#111}
#root{width:100%;height:100%;position:relative;overflow:hidden}
#stage{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.phrase{position:absolute;left:60px;right:120px;top:270px;bottom:670px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:0}
.line{overflow:hidden;line-height:.92;padding:0 8px}
.w{display:inline-block;font-family:Anton,sans-serif;text-transform:uppercase;color:#f4f1ea;white-space:nowrap}
.hero{color:#ff4d2e}
.flip .w{color:#fff}.flip .hero{color:#111}
.small{font-family:Inter,system-ui,sans-serif;font-weight:800;text-transform:none}
#bg{position:absolute;inset:0;background:#111}
</style></head>
<body><div id="root" data-composition-id="main" data-start="0" data-duration="8" data-width="1080" data-height="1920">
<div id="bg"></div><div id="stage"></div></div>
<script>
// [time, lines, moveName, heroWordIndex, flipBackground?] -- edit this list; times in seconds
const PHRASES = [
[0.2, ["WORDS"], "slam", 0],
[1.0, ["can", "MOVE"], "stack", 1],
[2.0, ["LIKE", "MUSIC"], "stretch", 1, true],
[3.2, ["one", "idea"], "stack", 1],
[4.2, ["AT A", "TIME"], "slam", 1],
[5.4, ["KINETIC", "TYPE"], "cascade", 1],
];
const END = 8, W = 1080 - 180;
// measure text only after the web fonts load, then build and register the timeline
document.fonts.ready.then(() => {
const tl = gsap.timeline({ paused: true });
const stage = document.getElementById("stage");
PHRASES.forEach(([t, lines, move, hero, flip], i) => {
const p = document.createElement("div"); p.className = "phrase" + (flip ? " flip" : ""); stage.appendChild(p);
const words = lines.map((txt, k) => {
const line = document.createElement("div"); line.className = "line";
const w = document.createElement("span"); w.className = "w" + (k === hero ? " hero" : "") + (txt === txt.toLowerCase() && k !== hero ? " small" : "");
w.textContent = txt; line.appendChild(w); p.appendChild(line);
w.style.fontSize = "100px"; const fit = Math.min(420, (100 * W) / w.getBoundingClientRect().width); // fill the width
w.style.fontSize = (w.classList.contains("small") ? Math.min(fit, 110) : fit) + "px";
return w;
});
gsap.set(p, { autoAlpha: 0 });
tl.set(p, { autoAlpha: 1 }, t);
if (move === "slam") tl.from(words, { scale: 2.6, opacity: 0, duration: 0.22, ease: "expo.out", stagger: 0.08 }, t)
.fromTo("#stage", { x: -10 }, { x: 10, duration: 0.04, yoyo: true, repeat: 3, ease: "none" }, t + 0.18).set("#stage", { x: 0 }, t + 0.34);
if (move === "stack") tl.from(words, { yPercent: 110, duration: 0.35, ease: "power4.out", stagger: 0.07 }, t);
if (move === "stretch") tl.from(words, { scaleY: 1.7, opacity: 0, transformOrigin: "50% 100%", duration: 0.3, ease: "back.out(3)", stagger: 0.08 }, t);
if (move === "cascade") words.forEach((w, k) => {
const letters = Array.from(w.textContent); w.textContent = "";
letters.forEach((c) => { const l = document.createElement("span"); l.style.display = "inline-block"; l.textContent = c; w.appendChild(l); });
tl.from(w.children, { y: -140, rotation: -20, opacity: 0, duration: 0.4, ease: "back.out(2)", stagger: 0.03 }, t + k * 0.15); });
const next = PHRASES[i + 1] ? PHRASES[i + 1][0] : null;
if (flip) tl.set("#bg", { background: "#ff4d2e" }, t).set("#bg", { background: "#111" }, next || END); // colour flip on a key phrase
if (next) tl.to(words, { yPercent: -110, opacity: 0, duration: 0.15, ease: "power2.in", stagger: 0.03 }, next - 0.17)
.set(p, { autoAlpha: 0 }, next);
});
tl.fromTo("#root", { scale: 1 }, { scale: 1.04, duration: END, ease: "none" }, 0); // slow drift
tl.set({}, {}, END);
window.__timelines["main"] = tl;
});
</script></body></html>
単語や時間を変更するには、PHRASESを編集します。動きを追加するには、動きの種類に基づいてif (move === ...)ブランチを追加します。ナレーションがある場合は、ルートに<audio>要素を配置し、トランスクリプトからフレーズの時間を取得します。
ユーザーに公開するパラメーター
aspect · phrases(time、lines、move、hero)· fontDisplay · fontSupport · colors(bg、text、accent)· tempo or audio · flipPhrases(背景を切り替える箇所)· endLockup。
避けること
- メインの動きとしてフェードインやゆっくりしたドリフトを使うこと。スライドショーのように見えてしまいます。
- 画面に一度に4語または3行を超えて表示すること。
- アクセントカラーを複数使うこと。アクセントカラーは1色にし、ヒーローワードにのみ使います。
- ヒーローワードに細いウェイトやライトウェイトのフォント、またはスクリプトフォントを使うこと。
- 9:16のフレームで、下端から670 px以内または上端から270 px以内にテキストを配置すること(プラットフォームのUIに隠れます)。
- ランダムな変化やレンダリングごとの変化を加えること。すべての値はタイムラインから取得する必要があります。
失敗しやすいパターン
| こう見える | 原因 | 対処 |
|---|---|---|
| スライドショー | フェード、長すぎる静止、毎回同じ動き | 語彙から動きを変化させます。登場は 0.35 秒未満、退場は 0.15 秒にします |
| デザインではなく字幕 | 文字が小さすぎる、何もない空間の中央に配置されている | フレーム幅に合わせて文字を大きくし、コンデンス書体を使います。250+ px にします |
| ごちゃごちゃして読みにくい | 一度に表示する文字や動きが多すぎる | 画面上のフレーズは 1 つにし、主役となる単語を 1 つ置きます。着地後は 0.35 秒静止させます |
| ビートからずれている | フレーズのタイミングを推測している | トランスクリプトまたはビートのタイミングを使い、ヒットは 2〜3 フレーム早く着地させます |
| マスクで文字が切れる | フォントに対して行の高さが狭すぎる | .line にパディングを設定して、line-height を 0.92〜1.0 にします。スナップショットを確認します |
| レンダリングでフォントが違う | フォントが読み込まれていない | head に Google Fonts の <link> を追加します。スナップショットでコンデンス書体が表示されているか確認します |
最終レンダリングの前に検証する
npx hyperframes check
npx hyperframes snapshot --at <one time per phrase 0.3 s after it lands, plus one mid-exit>
npx hyperframes render -o renders/<name>.mp4
ffprobe -v error -show_entries stream=width,height,r_frame_rate:format=duration -of csv=p=0 renders/<name>.mp4
すべてのスナップショットと snapshots/contact-sheet.jpg を確認し、次の点を確かめます。
- すべてのフレーズが幅の 50〜80% を占め、スマートフォンサイズでも読みやすい
- 各フレーズに主役となる単語が 1 つあり、アクセントカラーまたは異なる動きで強調されている
- どのフレームでも、2 つのフレーズが重なっていない
- すべてのテキストが、アスペクト比に応じたセーフエリア内にある
- 終わりのロックアップを少なくとも 1 秒表示し、冒頭と一致させている
- サイズと長さが依頼内容と一致している
最終レンダリングの前に自己批評する
スナップショットシートを、フック(最初の 1 秒)、スマートフォン幅での読みやすさ、動きの質、構図、正確さ、「ミュートテスト」(音なしで視聴者が一文で要約できるか)の各項目で 1〜10 点で評価します。7 点以下の項目だけを修正し、スナップショットを再取得して、すべてが 8 点以上になるまで繰り返します。可能であれば、評価は別の読み取り専用パスに任せ、自分の作業を自分で採点しないようにします。
| しないこと | 代わりにすること |
|---|---|
| 2 つのごちゃごちゃしたレイアウトをクロスフェードする | 古いコンテンツを退場させてから新しいものを登場させるか、オブジェクトを 1 つ引き継ぎます |
| 複数のものを同時に動かす | 動きに段階をつけ、その瞬間の焦点を 1 つにします |
| 間を埋めるために浮遊や揺れを加える | 静止させるか、キャラクターを動かします |
| 「勢い」を出すためにパーティクル、虹色のグラデーション、3D フリップを加える | より強い演出を加えます。たとえば、明かす、ヒットさせる、カメラを動かすなどです |
| 時間に収めるためにテキストを急がせる | 文字を削ります |
納品する
- ポスター:最も仕上がりの良いフレームをフレーム 0 に焼き込み、サムネイルに表示されるようにします。
ffmpeg -ss <t> -i renders/<name>.mp4 -frames:v 1 poster.jpgを実行してから、ffmpeg -i renders/<name>.mp4 -i poster.jpg -filter_complex "[0:v][1:v]overlay=enable='eq(n,0)'" -c:a copy renders/<name>-final.mp4を実行します。 - 投稿用のキャプションを 1〜2 文で用意します。手法ではなく、アイデアの内容を述べます。
- フレーズとタイミングの一覧、および修正時に変更するフレーズをユーザーに伝えます。
参考資料
- @8co28、JIZURA:ビートに合わせて単語のタイミングを指定できる歌詞テキスト動画ジェネレーター:https://x.com/8co28/status/2102572322016370781
- @deedydas:エッセイを画面上のタイポグラフィにした作品:https://x.com/deedydas/status/2104151743907160546
- @stephanlivera:1 行のプロンプトから作られた、タイポグラフィ主体のモーションショーリール:https://x.com/stephanlivera/status/2103315922098470926
- @deedydas:パンチの効いた見出しのスラム演出を軸にしたローンチ動画:https://x.com/deedydas/status/2102787937482252537
- HyperFrames のモーションの原則と顔出しなし解説動画のスキル、JohnHeibel/ClaudeAnimationBase のアニメーションガイド、howseen-ai/claude-motion-design の批評ループ、latent-spaces/brag からアイデアを取り入れたものです(コピーではありません)。