デザイン

キネティックタイポグラフィの作り方|CSSとGSAPの実装手順

この記事の対象: 文字を動かす表現を実装したいWebデザイナー・フロントエンドエンジニア
読了時間: 約9分

キネティックタイポグラフィは、テキストそのものをアニメーションさせる表現です。作り方は大きく3つ。CSSの@keyframesで1文字ずつ遅延させる方法、CSSのスクロール駆動アニメーションを使う方法、GSAPのSplitTextで文字・単語・行に自動分割する方法です。

この記事では最短で動くコードを先に置き、そのあとで「どれを選ぶか」「日本語で踏む罠」「読みやすさを壊さない線引き」まで順に解説します。コードはそのままコピーして動く形で載せています。


キネティックタイポグラフィの作り方(最短のコード)

まず動くものを作ります。やることは2つだけです。テキストを1文字ずつの要素に分ける、分けた要素に遅延付きのアニメーションを当てる。

文字が動くまでの4工程
文字を分けるJSで1文字ずつspanに包む
番号を振るCSS変数 –i に順番を入れる
遅延を計算delayを –i から算出する
配慮を足す読み上げと動き抑制に対応

分割を手書きのHTMLでやらないのが肝心です。コピーを直すたびにspanを数え直す作業が発生し、必ず破綻します。

HTML

<h1 class="kt" data-kinetic>文字が動くという体験</h1>

JavaScript(文字分割)

// 日本語を安全に1文字ずつ分割する
function splitChars(el) {
  const text = el.textContent.trim();
  const seg = new Intl.Segmenter('ja', { granularity: 'grapheme' });

  el.textContent = '';
  let i = 0;

  for (const { segment } of seg.segment(text)) {
    const span = document.createElement('span');
    span.className = 'kt-char';
    // 半角スペースはinline-blockだと潰れるので実体参照に置き換える
    span.textContent = segment === ' ' ? '\u00a0' : segment;
    span.style.setProperty('--i', i++);
    span.setAttribute('aria-hidden', 'true');
    el.appendChild(span);
  }

  // 分割後も元の1文として読み上げられるようにする
  el.setAttribute('aria-label', text);
}

document.querySelectorAll('[data-kinetic]').forEach(splitChars);

Intl.Segmenterを使うのがポイントです。text.split('')や[...text]では、絵文字・結合文字・異体字セレクタが途中で割れて別の文字に化けます。「👨‍👩‍👧」が3つに分解されたり、濁点が本体から離れたりする事故は、これで防げます。主要ブラウザの現行版であればそのまま使える機能ですが、対応状況は変わり続けるため、古い環境もサポート対象に含める場合は導入前に手元で確認してください。

CSS(アニメーション)

.kt-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.4em);
  animation: ktUp 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  /* 1文字あたり40msずつ遅らせる */
  animation-delay: calc(var(--i) * 40ms);
  /* 合成だけで動かし、レイアウトを再計算させない */
  will-change: transform, opacity;
}

@keyframes ktUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 動きを嫌うユーザーには最終状態だけ見せる */
@media (prefers-reduced-motion: reduce) {
  .kt-char {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

動かすのはtransformとopacityの2つに限ります。topやmarginを動かすとフレームごとにレイアウト計算が走り、文字数が多いほど確実にカクつきます。

実装のポイントはcalc(var(--i) * 40ms)です。従来はnth-child(1)から文字数ぶん並べていましたが、CSS変数に順番を持たせればコピーの長さが変わっても1行も直す必要がありません。1文字あたり30〜50msが見やすい範囲で、それ以上遅くすると読み終わるまで待たされる感覚が出ます。


3つの実装方法をどう選ぶか

やりたい動きの複雑さと、保守する人が誰かで決まります。

方法 向いている表現 追加の読み込み つまずきやすい点
CSS @keyframes 出現・スライド・フェード なし 連続した動きの制御が難しい
CSS スクロール駆動 スクロール連動の拡大・回転 なし 対応ブラウザに差がある
GSAP SplitText 行マスク・3D回転・順序制御 数十KB程度(バージョンにより変動) 画面幅変化時の再分割
CSSで足りる場合とGSAPが必要な場合

CSSで足りる

  • 1回だけ出現させる
  • 動きが1種類
  • ライブラリを増やしたくない
  • 更新を他社が引き継ぐ

GSAPが要る

  • 複数の動きを連結する
  • 行マスクで滑り出させる
  • スクロール位置で巻き戻す
  • タイムラインで秒単位に詰める

迷ったらCSSから始めるのが安全です。ファーストビューのコピーを1回出現させるだけなら、GSAPを入れる理由はありません。読み込むJavaScriptが増えるぶん、LCPに不利になります。

CSSのスクロール駆動アニメーション

スクロール量に文字の動きを紐づける表現は、JavaScriptなしで書けるようになりました。

@supports (animation-timeline: view()) {
  .kt-scroll .kt-char {
    animation: ktUp linear both;
    animation-timeline: view();
    /* 画面に入り始めて10%〜60%の区間で動かす */
    animation-range: entry 10% entry 60%;
    animation-delay: calc(var(--i) * -0.02s);
  }
}

Chrome・Edgeは対応済みですが、SafariとFirefoxは実装状況が動いている領域なので、必ず@supportsで囲んで未対応時は静止した最終状態が見えるようにします。囲まずに書くと、未対応ブラウザでopacity: 0のまま文字が消えます。これが一番多い事故です。

GSAP SplitTextで本格的に組む

GSAPは現在、SplitTextを含む全プラグインが無料で使えます(以前はClubメンバー限定の有料プラグインでした)。古い解説記事には有料前提の情報が残っていることがあるので、導入時は公式ドキュメントで最新の提供条件を確認してください。

import { gsap } from 'gsap';
import { SplitText } from 'gsap/SplitText';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(SplitText, ScrollTrigger);

SplitText.create('.hero-title', {
  type: 'lines,chars',
  mask: 'lines',        // 行ごとに隠し枠を作り、下から滑り出させる
  autoSplit: true,      // フォント読込後と画面幅変化時に再分割する
  onSplit(self) {
    return gsap.from(self.chars, {
      yPercent: 110,
      opacity: 0,
      stagger: 0.025,
      duration: 0.9,
      ease: 'power3.out',
      scrollTrigger: {
        trigger: '.hero-title',
        start: 'top 85%',
      },
    });
  },
});

autoSplitとonSplitの組み合わせが実務では効きます。手で分割すると、Webフォントが読み終わって行の折り返し位置が変わった瞬間に、行マスクと実際の行がズレます。再分割をライブラリに任せると、この崩れが起きません。

GSAPの基礎から確認したい場合はこちらにまとめています。

After Effectsで作った動きをそのまま載せたいときはLottieが選択肢になります。デザイナーが動き自体を作れる反面、JSONのファイルサイズ管理が必要です。詳しくは「LottieアニメーションをWebに組み込む方法」を参照してください。


日本語のキネティックタイポグラフィで踏む3つの罠

英語の解説記事をそのまま真似すると必ずここで詰まります。

改行位置が意図しない場所で崩れる

display: inline-blockを1文字ずつに当てると、ブラウザは各文字を独立したボックスとして扱います。結果、「お問い合わせください」が「お問い合わ/せください」のように意味の切れ目を無視して折れます。禁則処理も効きません。

対策は2つです。1行に収まる短いコピーだけ文字単位で分割するか、文字ではなく行・文節の単位で分割して、折り返し位置を自分で決めてしまうことです。

.kt {
  /* 単語の途中で折らせない */
  word-break: keep-all;
  overflow-wrap: anywhere;
}

長いコピーを文字単位で動かしたいなら、<br>か<wbr>で折り返し位置を先に確定させてから分割します。幅任せにすると、PCとスマホで別の場所が割れます。

縦書きとルビが壊れる

writing-mode: vertical-rlの見出しに文字分割を当てると、transformの方向感覚が90度ずれます。縦書きではtranslateYが横移動になるため、論理方向に沿うtranslateを使うか、縦書きの見出しは行単位のマスクに切り替えるのが現実的です。ルビ(<ruby>)を含むテキストは分割時に親子構造が壊れるので、分割対象から外します。

読み上げが文字の羅列になる

1文字ずつspanに分けた時点で、スクリーンリーダーは「も」「じ」「が」と1字ずつ読むことがあります。上のコードでaria-labelに元の文を入れ、子要素をaria-hidden="true"にしているのはこのためです。見た目の実装を足したことで情報が削られるのは、本末転倒です。


どこに使うと効くのか

動かす場所は絞ったほうが効きます。全部動くページは、ただ読みにくいページです。

ファーストビューのキャッチコピー

もっとも定番で、効果もはっきり出ます。ページの主役である1文に動きを加え、残りは静止させる。この配分が基本形です。1カラムで縦に読ませる構成とは特に相性がよく、最初の画面で世界観を決めてから本文に入れます。

セクションの切り替わり

スクロールで到達したタイミングで見出しが出現すると、章の区切りがはっきりします。グリッドで情報を整列させた構成では、見出しだけが動くことで静的なグリッドとの対比が生まれます。グリッドレイアウトのWebデザイン実例9選で紹介しているような整った枠組みに1点だけ動きを置くのが、崩れにくい使い方です。

ローディング画面

読み込み中にブランド名を動かす表現です。待ち時間を体験に変えられますが、表示を遅らせる演出はLCPの悪化に直結します。実データの到着を待たせる作りにはしないでください。

業種で見た効きやすさ

Soreiine!!に掲載されているサイトを見ていくと、文字を動かす表現は制作会社・デザイン事務所、アート・展覧会、アパレルといった業種で目立つ傾向があります。いずれも「何を売るか」より「どういう感覚の相手か」を先に伝える必要がある業種で、テキストの動き方そのものがトーンの説明になっています。

逆に、医療・金融・不動産のように情報の正確さが評価軸になる業種では、文字の動きは控えめか、使われていないことのほうが多い印象です。動きが「軽さ」として読まれてしまうためです。業種ごとの傾向は以下で分類しています。

背景を暗く締めると、文字の動きが光の演出として読めるようになります。配色から設計する場合は「ブラック系Webデザインの配色事例10選【2026年】」、アート寄りの文脈なら「アート・美術館・展覧会Webサイトデザイン事例|6件を分類」が参考になります。


公開前に見る5項目

実装が終わってから確認する点をまとめます。1つでも抜けると、特定の環境で文字が読めなくなります。

  • 未対応・JS無効時に文字が見えるか — 初期値opacity: 0をCSSに直書きせず、JSが分割に成功したときだけ付与する
  • prefers-reduced-motionで止まるか — OSの設定を実際に切り替えて確認する
  • スマホ幅で改行位置が崩れていないか — 全段落を目で読む。自動検出では見つからない
  • フォント読込後にズレないか — font-display: swapと行マスクの組み合わせは崩れやすい
  • スクリーンリーダーで1文として読まれるか — aria-labelの有無で挙動が変わる

CSS変数の扱いに慣れておくと、遅延やイージングを一箇所で管理できて保守が楽になります。


よくある質問

キネティックタイポグラフィとは何ですか

文字そのものにモーションを加える表現手法です。モーションデザインとタイポグラフィを組み合わせたもので、映像やプレゼンの分野で先に発達し、ブラウザの性能向上によってWebでも常用されるようになりました。注目を集める、トーンを伝える、登場順で物語を作る、という3つの効果があります。

CSSだけで作れますか

作れます。文字を1つずつの要素に分ける処理だけはJavaScriptが必要ですが、動き自体は@keyframesとanimation-delayで完結します。スクロール連動もanimation-timeline: view()で書けるようになりました。ライブラリを足すのは、複数の動きを秒単位で連結したい場合だけで十分です。

日本語でも問題なく使えますか

使えますが、英語とは注意点が違います。1文字ずつinline-blockにすると改行位置がブラウザ任せになり、意味の切れ目を無視して折れます。短いコピーに限定するか、文節・行の単位で分割して折り返しを自分で決めてください。縦書きとルビは分割対象から外すのが無難です。

パフォーマンスへの影響はどの程度ですか

transformとopacityだけを動かす限り、文字数が多いページでも体感できる負荷は出にくいです。問題になるのはtopやwidthを動かした場合と、GSAPを読み込んだぶんのJavaScript増加です。ファーストビューで使うならCSS実装を選ぶほうがLCPには有利です。

アクセシビリティで最低限やるべきことは何ですか

2つあります。prefers-reduced-motion: reduceで動きを止め最終状態を表示すること、分割した要素をaria-hiddenにして親にaria-labelで元の文を持たせることです。前者は動きに体調が左右されるユーザーへの配慮、後者は読み上げが1字ずつにならないための対応です。


まとめ

  • 作り方は文字を分ける→番号を振る→calc(var(--i) * 40ms)で遅らせるの3手順
  • 分割はIntl.Segmenterを使う。split('')は絵文字と結合文字が割れる
  • CSSで足りるかGSAPが要るかは、動きを連結するかどうかで決まる
  • GSAPはSplitTextが無料で使える。autoSplitでフォント読込後のズレを防ぐ
  • 日本語は改行位置が崩れるのが最大の罠。折り返しは自分で決める
  • prefers-reduced-motionとaria-labelは実装の一部として最初から入れる

動きは、ページ全体ではなく主役の1文に寄せたときに効きます。2026年のトレンド全体での位置づけは「Webデザイントレンド2026」で整理しています。