キネティックタイポグラフィ
大きな単語が拡大・回転・スライドしながら次々と飛び込み、躍動的に入れ替わるループ演出をJSで実装します。語ごとにアニメ種別を切り替えて単調さを抑え、入場と退場をなめらかに繋ぎます。ヒーローやオープニングの強いキャッチに最適です。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<div class="page">
<header class="nav">
<div class="brand"><span class="petal"></span>Sakura</div>
<span class="tag">OPENING</span>
</header>
<section class="hero">
<p class="eyebrow">2nd ANNIVERSARY</p>
<div class="stage">
<span class="word"></span>
</div>
<p class="lead">桜、2周年。<br>新しい季節へ、全力で駆けぬける。</p>
</section>
</div>
CSS
/* Sakura:キネティックタイポのオープニングが主役 */
:root { --dur: 1.5; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Yu Gothic", "Segoe UI", sans-serif;
background:
radial-gradient(600px 340px at 50% -10%, #ffe3ec 0%, transparent 60%),
linear-gradient(165deg, #fff5f8 0%, #ffe0ec 100%);
color: #5a3a46;
min-height: 400px;
overflow: hidden;
}
.page { padding: 16px 26px; }
.nav { display: flex; align-items: center; justify-content: space-between; }
.brand { display: flex; align-items: center; gap: 9px; font-weight: 800; font-size: 16px; color: #e8638c; }
.petal { width: 14px; height: 14px; background: #ffd1e0; border-radius: 50% 0 50% 50%; transform: rotate(45deg); box-shadow: 0 0 8px rgba(232,99,140,0.4); }
.tag { font-size: 11px; color: #e8638c; letter-spacing: 0.3em; font-weight: 700; }
.hero { text-align: center; padding: 18px 6px 0; }
.eyebrow { font-size: 11px; letter-spacing: 0.3em; color: #c97a93; font-weight: 700; }
/* 単語が飛び込むステージ */
.stage {
margin-top: 8px;
height: 150px;
display: grid; place-items: center;
overflow: hidden;
}
.word {
font-size: clamp(48px, 13vw, 100px);
font-weight: 900; letter-spacing: 0.02em;
color: #e8638c;
text-shadow: 0 8px 20px rgba(232,99,140,0.3);
will-change: transform, opacity;
}
/* 入場アニメ種別(語ごとに切り替え) */
.in-scale { animation: inScale var(--dur) cubic-bezier(0.2,0.8,0.2,1) both; }
.in-spin { animation: inSpin var(--dur) cubic-bezier(0.2,0.8,0.2,1) both; }
.in-slide { animation: inSlide var(--dur) cubic-bezier(0.2,0.8,0.2,1) both; }
.in-flip { animation: inFlip var(--dur) cubic-bezier(0.2,0.8,0.2,1) both; }
@keyframes inScale {
0% { opacity: 0; transform: scale(0.2); }
20%, 80% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(1.6); }
}
@keyframes inSpin {
0% { opacity: 0; transform: rotate(-90deg) scale(0.4); }
20%, 80% { opacity: 1; transform: rotate(0) scale(1); }
100% { opacity: 0; transform: rotate(40deg) scale(0.5); }
}
@keyframes inSlide {
0% { opacity: 0; transform: translateX(-60%); }
20%, 80% { opacity: 1; transform: translateX(0); }
100% { opacity: 0; transform: translateX(60%); }
}
@keyframes inFlip {
0% { opacity: 0; transform: perspective(500px) rotateX(90deg); }
20%, 80% { opacity: 1; transform: perspective(500px) rotateX(0); }
100% { opacity: 0; transform: perspective(500px) rotateX(-90deg); }
}
.lead { margin-top: 6px; font-size: 13.5px; line-height: 1.8; color: #8a6573; }
@media (prefers-reduced-motion: reduce) {
.word { animation: none !important; opacity: 1 !important; transform: none !important; }
}
JavaScript
// 大きな語を躍動的に入れ替えるループ(オープニング)
(function () {
const el = document.querySelector('.word');
if (!el) return; // null安全
// 桜の世界観に合わせた語
const words = ['SAKURA', '満開', 'LIVE', '2nd', '咲け', 'GO!'];
const types = ['in-scale', 'in-spin', 'in-slide', 'in-flip'];
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) { el.textContent = 'SAKURA'; return; } // 控えめ設定は静的
// CSS変数 --dur(秒)を1語の表示時間に
const css = getComputedStyle(document.documentElement);
const durSec = parseFloat(css.getPropertyValue('--dur')) || 1.5;
const STEP = Math.max(700, durSec * 1000);
let i = 0;
function show() {
const word = words[i % words.length];
const type = types[i % types.length];
el.className = 'word';
el.textContent = word;
void el.offsetWidth; // リフローで再トリガ
el.classList.add(type);
i += 1;
}
show();
setInterval(show, STEP);
})();
コード
HTML
<main class="stage">
<p class="kicker">KINETIC TYPE</p>
<!-- 大きな単語が時間で躍動的に入れ替わるステージ -->
<div class="kinetic" aria-live="polite">
<span class="word" data-words></span>
</div>
<p class="caption">語が拡大・回転・スライドして躍動</p>
</main>
CSS
/* 全体リセットと暗い背景 */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-0: #0b0d18;
--bg-1: #161a33;
--accent: #ff5d8f;
--accent-2: #5eead4;
--ink: #f5f7ff;
--dim: #8b93b8;
--dur: 1.6s; /* 1語あたりの表示時間(JSと共有) */
}
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
/* 奥行きのある放射状+斜めグラデ背景 */
background:
radial-gradient(900px 420px at 50% -20%, #25305c 0%, transparent 60%),
linear-gradient(160deg, var(--bg-0) 0%, var(--bg-1) 100%);
color: var(--ink);
overflow: hidden;
}
.stage { text-align: center; padding: 24px; width: 100%; }
.kicker {
font-size: 12px;
letter-spacing: 0.5em;
font-weight: 700;
color: var(--dim);
margin-bottom: 18px;
padding-left: 0.5em; /* レタースペース分の視覚補正 */
}
/* 大きな単語を中央に固定表示する枠 */
.kinetic {
position: relative;
height: clamp(80px, 22vw, 150px);
display: grid;
place-items: center;
perspective: 700px; /* 回転に奥行きを与える */
}
.word {
display: inline-block;
font-size: clamp(48px, 13vw, 110px);
font-weight: 900;
line-height: 1;
letter-spacing: -0.03em;
white-space: nowrap;
/* グラデで塗った躍動感のある文字 */
background: linear-gradient(100deg, var(--accent) 0%, #b07bff 50%, var(--accent-2) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 8px 26px rgba(176, 123, 255, 0.35));
will-change: transform, opacity;
}
/* === 入場アニメ(JSがクラスを付け替える) === */
.word.in-scale { animation: kScale var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-spin { animation: kSpin var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-slide { animation: kSlide var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-flip { animation: kFlip var(--dur) cubic-bezier(.2,.9,.2,1) both; }
/* 拡大しながら登場→少し縮んで退場 */
@keyframes kScale {
0% { opacity: 0; transform: scale(2.4); filter: blur(6px); }
18% { opacity: 1; transform: scale(1); filter: blur(0); }
80% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.4); filter: blur(4px); }
}
/* 回転しながら登場→逆回転で退場 */
@keyframes kSpin {
0% { opacity: 0; transform: rotate(-90deg) scale(0.5); }
20% { opacity: 1; transform: rotate(0deg) scale(1); }
80% { opacity: 1; transform: rotate(0deg) scale(1); }
100% { opacity: 0; transform: rotate(60deg) scale(0.6); }
}
/* 横から滑り込み→反対へ滑り去る */
@keyframes kSlide {
0% { opacity: 0; transform: translateX(120%) skewX(-12deg); }
20% { opacity: 1; transform: translateX(0) skewX(0); }
80% { opacity: 1; transform: translateX(0) skewX(0); }
100% { opacity: 0; transform: translateX(-120%) skewX(12deg); }
}
/* X軸でめくれて登場→さらにめくれて退場 */
@keyframes kFlip {
0% { opacity: 0; transform: rotateX(90deg) translateY(20px); }
20% { opacity: 1; transform: rotateX(0) translateY(0); }
80% { opacity: 1; transform: rotateX(0) translateY(0); }
100% { opacity: 0; transform: rotateX(-90deg) translateY(-20px); }
}
.caption {
margin-top: 18px;
font-size: 14px;
color: var(--dim);
letter-spacing: 0.04em;
}
/* モーション控えめ設定への配慮(任意) */
@media (prefers-reduced-motion: reduce) {
.word { animation-duration: 0.01ms !important; }
}
JavaScript
// 大きな単語を時間で躍動的に入れ替えるループ
(function () {
const el = document.querySelector('.word');
if (!el) return; // null安全
// 表示する単語と入場アニメ種別のローテーション
const words = ['MOVE', 'PLAY', 'BOLD', 'FLOW', 'RISE', 'GO!'];
const types = ['in-scale', 'in-spin', 'in-slide', 'in-flip'];
// CSS変数 --dur(秒)を読み取り、1語の表示時間とする
const css = getComputedStyle(document.documentElement);
const durSec = parseFloat(css.getPropertyValue('--dur')) || 1.6;
const STEP = Math.max(600, durSec * 1000); // ms
let i = 0;
function show() {
const word = words[i % words.length];
const type = types[i % types.length];
// 一旦アニメをリセットしてから付け直す(再トリガ)
el.className = 'word';
el.textContent = word;
// 強制リフローでアニメを確実に再生
void el.offsetWidth;
el.classList.add(type);
i += 1;
}
show();
// 表示時間ごとに次の語へ
setInterval(show, STEP);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「キネティックタイポグラフィ」の効果を追加してください。
# 追加してほしい効果
キネティックタイポグラフィ(タイポグラフィ)
大きな単語が拡大・回転・スライドしながら次々と飛び込み、躍動的に入れ替わるループ演出をJSで実装します。語ごとにアニメ種別を切り替えて単調さを抑え、入場と退場をなめらかに繋ぎます。ヒーローやオープニングの強いキャッチに最適です。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<main class="stage">
<p class="kicker">KINETIC TYPE</p>
<!-- 大きな単語が時間で躍動的に入れ替わるステージ -->
<div class="kinetic" aria-live="polite">
<span class="word" data-words></span>
</div>
<p class="caption">語が拡大・回転・スライドして躍動</p>
</main>
【CSS】
/* 全体リセットと暗い背景 */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-0: #0b0d18;
--bg-1: #161a33;
--accent: #ff5d8f;
--accent-2: #5eead4;
--ink: #f5f7ff;
--dim: #8b93b8;
--dur: 1.6s; /* 1語あたりの表示時間(JSと共有) */
}
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
/* 奥行きのある放射状+斜めグラデ背景 */
background:
radial-gradient(900px 420px at 50% -20%, #25305c 0%, transparent 60%),
linear-gradient(160deg, var(--bg-0) 0%, var(--bg-1) 100%);
color: var(--ink);
overflow: hidden;
}
.stage { text-align: center; padding: 24px; width: 100%; }
.kicker {
font-size: 12px;
letter-spacing: 0.5em;
font-weight: 700;
color: var(--dim);
margin-bottom: 18px;
padding-left: 0.5em; /* レタースペース分の視覚補正 */
}
/* 大きな単語を中央に固定表示する枠 */
.kinetic {
position: relative;
height: clamp(80px, 22vw, 150px);
display: grid;
place-items: center;
perspective: 700px; /* 回転に奥行きを与える */
}
.word {
display: inline-block;
font-size: clamp(48px, 13vw, 110px);
font-weight: 900;
line-height: 1;
letter-spacing: -0.03em;
white-space: nowrap;
/* グラデで塗った躍動感のある文字 */
background: linear-gradient(100deg, var(--accent) 0%, #b07bff 50%, var(--accent-2) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 8px 26px rgba(176, 123, 255, 0.35));
will-change: transform, opacity;
}
/* === 入場アニメ(JSがクラスを付け替える) === */
.word.in-scale { animation: kScale var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-spin { animation: kSpin var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-slide { animation: kSlide var(--dur) cubic-bezier(.2,.9,.2,1) both; }
.word.in-flip { animation: kFlip var(--dur) cubic-bezier(.2,.9,.2,1) both; }
/* 拡大しながら登場→少し縮んで退場 */
@keyframes kScale {
0% { opacity: 0; transform: scale(2.4); filter: blur(6px); }
18% { opacity: 1; transform: scale(1); filter: blur(0); }
80% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.4); filter: blur(4px); }
}
/* 回転しながら登場→逆回転で退場 */
@keyframes kSpin {
0% { opacity: 0; transform: rotate(-90deg) scale(0.5); }
20% { opacity: 1; transform: rotate(0deg) scale(1); }
80% { opacity: 1; transform: rotate(0deg) scale(1); }
100% { opacity: 0; transform: rotate(60deg) scale(0.6); }
}
/* 横から滑り込み→反対へ滑り去る */
@keyframes kSlide {
0% { opacity: 0; transform: translateX(120%) skewX(-12deg); }
20% { opacity: 1; transform: translateX(0) skewX(0); }
80% { opacity: 1; transform: translateX(0) skewX(0); }
100% { opacity: 0; transform: translateX(-120%) skewX(12deg); }
}
/* X軸でめくれて登場→さらにめくれて退場 */
@keyframes kFlip {
0% { opacity: 0; transform: rotateX(90deg) translateY(20px); }
20% { opacity: 1; transform: rotateX(0) translateY(0); }
80% { opacity: 1; transform: rotateX(0) translateY(0); }
100% { opacity: 0; transform: rotateX(-90deg) translateY(-20px); }
}
.caption {
margin-top: 18px;
font-size: 14px;
color: var(--dim);
letter-spacing: 0.04em;
}
/* モーション控えめ設定への配慮(任意) */
@media (prefers-reduced-motion: reduce) {
.word { animation-duration: 0.01ms !important; }
}
【JavaScript】
// 大きな単語を時間で躍動的に入れ替えるループ
(function () {
const el = document.querySelector('.word');
if (!el) return; // null安全
// 表示する単語と入場アニメ種別のローテーション
const words = ['MOVE', 'PLAY', 'BOLD', 'FLOW', 'RISE', 'GO!'];
const types = ['in-scale', 'in-spin', 'in-slide', 'in-flip'];
// CSS変数 --dur(秒)を読み取り、1語の表示時間とする
const css = getComputedStyle(document.documentElement);
const durSec = parseFloat(css.getPropertyValue('--dur')) || 1.6;
const STEP = Math.max(600, durSec * 1000); // ms
let i = 0;
function show() {
const word = words[i % words.length];
const type = types[i % types.length];
// 一旦アニメをリセットしてから付け直す(再トリガ)
el.className = 'word';
el.textContent = word;
// 強制リフローでアニメを確実に再生
void el.offsetWidth;
el.classList.add(type);
i += 1;
}
show();
// 表示時間ごとに次の語へ
setInterval(show, STEP);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。