この記事の対象: 文字を動かす表現を実装したいWebデザイナー・フロントエンドエンジニア
読了時間: 約9分
キネティックタイポグラフィは、テキストそのものをアニメーションさせる表現です。作り方は大きく3つ。CSSの@keyframesで1文字ずつ遅延させる方法、CSSのスクロール駆動アニメーションを使う方法、GSAPのSplitTextで文字・単語・行に自動分割する方法です。
この記事では最短で動くコードを先に置き、そのあとで「どれを選ぶか」「日本語で踏む罠」「読みやすさを壊さない線引き」まで順に解説します。コードはそのままコピーして動く形で載せています。
キネティックタイポグラフィの作り方(最短のコード)
まず動くものを作ります。やることは2つだけです。テキストを1文字ずつの要素に分ける、分けた要素に遅延付きのアニメーションを当てる。
分割を手書きの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で足りる
- 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」で整理しています。
