Communitygithub.com

MooT020/kinetic-typography

映像そのものが言葉となるキネティックタイポグラフィ動画を生成します。言葉が画面に打ち付けられ、伸び、積み重なり、分割され、入れ替わりながら、ナレーションや音楽のビートに合わせて9:16または16:9のMP4にレンダリングします。

kinetic-typography とは?

このスキルは、テキストのみで構成されたダイナミックなキネティックタイポグラフィ動画を作成するためのものです。言葉そのものが視覚的な主役となり、ナレーションや音楽のリズムに合わせて、激しく打ち付けられ、伸縮し、積み重なり、分割され、入れ替わるアニメーションを生成します。主にテキストのみの解説動画、フックやタイトルシーケンス、引用、歌詞や詩の動画、告知、そして単語単位のキャプションをデザインの中心として使用するのに適しています。他の映像に重ねる字幕(その場合はプラットフォームの字幕ツールを使用)や、長文の読み物が多いスライド(その場合はdata-story-chartsやsystem-diagram-animationを使用)には向いていません。このスキルはHyperFramesとGSAP(またはRemotion)をベースに構築され、Node.jsとFFmpegのみで動作します。視聴者が一瞬で理解できるよう、1フレーズ1~4単語、1フレーズに1つのヒーローワードを設定し、大胆な動きとコントラストでインパクトを与えます。ユーザーはアスペクト比、テンポ、トーン(punchy/calm/chaotic)、フォント、色などをカスタマイズ可能です。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/MooT020/takhayyal-video-skills/tree/HEAD/plugins/motion-design-styles/skills/kinetic-typography

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

キネティックタイポグラフィ

単語そのものが絵になる動画を作成します。フレーズが現れ、フレームを埋め尽くし、打ち付けられ、次のフレーズに取って代わられます。リズミカルに。まるで動くポスターのように、大胆で速く、音楽的であるべきです。

オプション

オプションデフォルト備考
フォーマット縦長 1080×1920または横長 1920×1080、正方形 1080×1080
長さ10–20 秒ナレーションまたは楽曲のセクションに合わせる
トーンパンチcalm(0.4秒のスライドとフェード、反転少なめ)、punchy(デフォルト)、chaotic(オールキャップス、激しい打ち付け、シェイク、フレーズごとの反転)
リズム固定テンポまたはナレーションの単語タイミング、または音楽のビート

準備(回答前に)

  1. 視聴者が覚えておくべき一文は何ですか?
  2. スクリプトを1~4単語のフレーズに分割し、フレーズごとに1つのヒーローワードをマークします。
  3. エネルギーのピーク(カラーフリップが発生するフレーズ)はどこですか?
  4. エンディングのロックアップ(タイトル、コールトゥアクション、ハンドル)は何ですか?

読みやすさシート(ビートリストと一緒に書く)

ビートごとに、視聴者が理解すべきこと(「ロボットは飛べない」「キャッシュミス」)と、それが読み取れる時間を1つだけ書きます。一度に1つの読み物。テキストの表示時間:ラベルは最低0.8秒、文章は完全に表示された後、単語あたり約0.3秒。時間内に収まらない場合は、速度を上げるのではなく単語を削ります。

モーションの原則

  • モーションには原因があります。すべての動きには画面上での理由(クリック、落下、パケットの到着)があり、次の動きはそれに続きます。
  • 動画全体で1つの主要な移動方向を選び、それを守ります。他の方向は意味を持たせて使用します:上は成功や結論、「全体像はここ」というズームアウト。
  • シーンの出口は次のエントリーを設定します:同じ軸、同じ方向、同じスピード。クロスフェードではなく、1つのオブジェクトを変化にわたって持続させます。
  • 図、チャート、テキストに無駄な動き(浮遊、呼吸、ふらつき)は入れません。何も動かす必要がなければ静止させます。意図的なカメラのドリフトが1つあれば十分です。
  • イージング:エントリーはスムーズなイーズアウト(power2.out から power4.out)、エグジットはより素早いイーズイン(エントリー時間の約75%)。エントリーは最大0.8秒。グループのスタッガーは最大0.5秒。オーバーシュート(back.out)は小さなポップのみ。弾性やバウンスは使用しません。
  • 重要な場面では一度に1つのものだけを動かします。目は1つしか追えません。
  • 見せ場の直前に0.3~0.75秒の静寂のビートを追加します。

スタイルルール(常に)

これらがこのスタイルを作ります。色や言葉が変わってもこれらは守ってください。

  1. タイプはフレーム幅の50~80%を占めます。小さなテキストはキャプションであり、ビジュアルではないからです。
  2. 一度に画面に表示するのは1つのアイデア:1~4単語、最大3行。他はすべて消えます。
  3. すべての単語は動き(スラム、スライド、ストレッチ、マスクリビール、フリップ)とともに現れます。単純なフェードインはなし。
  4. 変化はリズムに乗って起こります:0.25~0.7秒ごとに新しい単語または動き、キーワードはビートまたはアクセントのある音節に合わせます。
  5. コントラストは極端に:ほぼ黒からオフホワイト、またはその逆。キーワードのみに1つのアクセントカラー。
  6. 各フレーズには1つのヒーローワード:最大、アクセントカラー、または他の単語と異なる動き。

デフォルト(自由に変更可)

フォント、色、単語、タイミング、背景。ユーザーがブランドフォントや色を指定した場合は、そちらを優先します。優先順位:ユーザーのブランドまたは指定 > 明示的なリクエスト > これらのデフォルト。

  • 背景 #111111、テキスト #f4f1ea、アクセント #ff4d2e。フレーズごとに明暗を反転してエネルギーを出します。
  • ディスプレイフォント:太いコンデンスドフェイス(Anton、Bebas Neue、Archivo Black)。ヒーローワードはオールキャップス。サポートフォント:小さな接続詞用の太いサンセリフ(Inter 800)。
  • サイズ:9:16でヒーローワードは180~320 px。小さな単語は60~90 px。

セットアップ(一度だけ)

node -v                      # 22+が必要
ffmpeg -version
npx hyperframes init my-video --non-interactive --resolution portrait && cd my-video   # 16:9の場合はlandscape

既存の空のフォルダ内では npx hyperframes init . --non-interactive --resolution portrait を使用し、生成された index.html を以下のテンプレートで置き換えます。

構築前に決定すること

コードを書く前に、これらを1行でユーザーに伝えてください。

  1. アスペクト比: 9:16 (1080×1920) または 16:9 (1920×1080)。デフォルトは9:16。
  2. 単語とタイミング: スクリプトを1~4単語のフレーズに分割します。ナレーションがある場合は、その単語のタイムスタンプから時間を取得します(npx hyperframes transcribe voice.wav)。ない場合は、各フレーズに0.6~1.4秒を与え、ヒーローワードはより長くします。
  3. リズムソース: 音声(アクセントのある音節に合わせる)、音楽(ビートに合わせる;npx hyperframes beats music.mp3)、または固定テンポ(例:120 BPM = 0.5秒ごとにビート)。
  4. フレーズごとのヒーローワード と、それが使用する動き。
ニーズHyperFrames + GSAP (デフォルト)Remotion
単語と文字の動き、マスク、ストレッチはいはい
音声またはビートデータからのタイミングはい(ラベル)はい(フレーム)
Node と FFmpeg のみで動作はいはい
ライセンス無料3人までは無料、それ以上は有料

動きの語彙

動き方法(GSAP)用途
スラムfrom {scale: 2.6, opacity: 0}、expo.out、0.22 秒、さらに 6 px の画面シェイク 0.1 秒ヒーローワード
スライドスタック行を from {yPercent: 110} で overflow:hidden マスク内に、スタッガー 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% のスケールドリフト、ヒーローワードのヒット時にはパンチイン(スケール 1.06 で 0.1 秒)、スラム時には 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?]  -- このリストを編集; 時間は秒
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;
// テキストはWebフォントのロード後にのみ測定し、タイムラインを構築して登録する
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); // 幅を埋める
    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);   // キーフレーズでカラーフリップ
  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);         // ゆっくりとしたドリフト
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-heightがきつすぎるline-height 0.92~1.0、.line にパディング。スナップショットで確認
レンダリングでフォントが違うフォントがロードされていないhead内にGoogle Fontsの <link>。スナップショットでコンデンスドフェイスが表示されているか確認

最終レンダリング前に検証

npx hyperframes check
npx hyperframes snapshot --at <各フレーズの着地後0.3秒の時点、およびエグジット途中の時点を1つ>
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~10でスコアリングします:フック(最初の1秒)、電話幅での読みやすさ、モーションの質、構図、正確性、「ミュートテスト」(音声なしで視聴者が一文で要約できるか)。7以下のものだけを修正し、再スナップショットし、すべてが8以上になるまで繰り返します。可能であれば、自分で採点するのではなく、独立した読み取り専用パスでスコアリングを行います。

しないこと代わりにすること
2つの混雑したレイアウト間でクロスフェード古いコンテンツをエグジットし、新しいコンテンツをエントリーするか、1つのオブジェクトを持ち越す
複数のものを同時に動かすスタッガー、1つの焦点

…

[truncated for translation]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

関連スキル