文字ごと出現アニメ
JSでテキストを1文字ずつspan化し、遅延を付けて波打つように出現させます。ページロード時の見出し演出に向いています。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<div class="page">
<header class="nav">
<div class="brand"><span class="cup"></span>MOON BREW</div>
<nav class="links"><a>メニュー</a><a>店舗</a><a>about</a></nav>
</header>
<section class="hero">
<p class="eyebrow">SINCE 2014 · 自家焙煎</p>
<h1 class="reveal" data-text="月夜の一杯を。"></h1>
<p class="lead">深煎りの香りと、やわらかな灯り。<br>夜更けまで、あなたの時間に寄り添うコーヒーを。</p>
<a class="btn">本日のおすすめを見る</a>
</section>
</div>
CSS
/* MOON BREW:見出しの文字ごと出現が主役 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Hiragino Mincho ProN", "Yu Mincho", "Segoe UI", serif;
background:
radial-gradient(600px 320px at 78% -10%, #3a2817 0%, transparent 60%),
linear-gradient(165deg, #2b1d12 0%, #20140b 100%);
color: #f5ede1;
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: 700; font-size: 16px; letter-spacing: 0.08em; }
.cup {
width: 16px; height: 16px; border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #f3d9a8, #c98a3b);
box-shadow: 0 0 10px rgba(201,138,59,0.5);
}
.links { display: flex; gap: 16px; }
.links a {
font-size: 13px; color: #cdb593; cursor: pointer;
font-family: "Segoe UI", sans-serif;
}
.links a:hover { color: #f5ede1; }
.hero { text-align: center; padding: 34px 6px 0; }
.eyebrow {
font-size: 11px; letter-spacing: 0.28em; color: #c98a3b; font-weight: 600;
font-family: "Segoe UI", sans-serif;
}
/* 文字ごと出現する見出し本体 */
.reveal {
margin-top: 18px;
font-size: clamp(34px, 8vw, 60px);
font-weight: 700; line-height: 1.2; letter-spacing: 0.06em;
}
.reveal .ch {
display: inline-block;
opacity: 0;
transform: translateY(0.5em) rotate(6deg);
/* 文字順の遅延(--d)で波打つように立ち上がる */
animation: pop 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
animation-delay: var(--d, 0s);
color: #f5ede1;
}
.reveal .ch.space { width: 0.4em; }
@keyframes pop {
to { opacity: 1; transform: translateY(0) rotate(0); }
}
.lead {
margin-top: 18px; font-size: 13.5px; line-height: 1.9; color: #cdb593;
font-family: "Segoe UI", sans-serif;
}
.btn {
display: inline-block; margin-top: 22px;
background: #c98a3b; color: #2b1d12;
padding: 10px 24px; border-radius: 24px;
font-size: 13px; font-weight: 700; cursor: pointer;
font-family: "Segoe UI", sans-serif;
}
.btn:hover { background: #e0a04a; }
@media (prefers-reduced-motion: reduce) {
.reveal .ch { animation: none; opacity: 1; transform: none; }
}
JavaScript
// 見出しを1文字ずつ出現させる
(function () {
const title = document.querySelector('.reveal');
if (!title) return; // null安全
const text = title.dataset.text || '';
// 1文字ずつspan化して遅延を付与
function build() {
title.innerHTML = '';
[...text].forEach((char, i) => {
const span = document.createElement('span');
if (char === ' ' || char === ' ') {
span.className = 'ch space';
span.innerHTML = ' ';
} else {
span.className = 'ch';
span.textContent = char;
}
// 文字順に遅延を増やして波打つ出現に
span.style.setProperty('--d', (i * 0.07) + 's');
title.appendChild(span);
});
}
build();
})();
コード
HTML
<main class="stage">
<p class="kicker">CHAR REVEAL</p>
<!-- data-text の文字をJSで1文字ずつ<span>化する -->
<h1 class="reveal" data-text="Hello, World"></h1>
<button class="replay" type="button">もう一度再生</button>
</main>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Georgia", "Times New Roman", serif;
background:
radial-gradient(900px 400px at 80% 0%, #2d1b4e 0%, transparent 55%),
radial-gradient(900px 400px at 10% 100%, #1b3a4e 0%, transparent 55%),
#0c0c14;
color: #f5f0ff;
overflow: hidden;
}
.stage { text-align: center; padding: 24px; }
.kicker {
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 12px;
letter-spacing: 0.5em;
color: #8a7fb8;
margin-bottom: 18px;
padding-left: 0.5em;
}
.reveal {
font-size: clamp(40px, 10vw, 86px);
font-weight: 700;
line-height: 1.1;
letter-spacing: 0.01em;
/* 折り返しの空白を保持 */
white-space: pre-wrap;
}
/* JSが生成する各文字span。初期は下から透明 */
.reveal .ch {
display: inline-block;
opacity: 0;
transform: translateY(0.5em) rotate(8deg);
/* --d はJSが文字ごとに設定する遅延 */
animation: pop 0.6s cubic-bezier(.2, .8, .2, 1) forwards;
animation-delay: var(--d, 0s);
}
.reveal .space { width: 0.3em; } /* 半角スペース用の幅 */
@keyframes pop {
to { opacity: 1; transform: translateY(0) rotate(0); }
}
.replay {
margin-top: 30px;
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 13px;
color: #f5f0ff;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.18);
padding: 9px 20px;
border-radius: 999px;
cursor: pointer;
transition: background .2s, transform .1s;
}
.replay:hover { background: rgba(255,255,255,0.14); }
.replay:active { transform: scale(0.96); }
@media (prefers-reduced-motion: reduce) {
.reveal .ch { animation-duration: 0.01s; }
}
JavaScript
// 文字ごとに出現アニメさせる
(function () {
const title = document.querySelector('.reveal');
const btn = document.querySelector('.replay');
if (!title) return; // null安全
const text = title.dataset.text || '';
// テキストを1文字ずつ<span>に分解して描画
function build() {
title.innerHTML = '';
[...text].forEach((char, i) => {
const span = document.createElement('span');
if (char === ' ') {
span.className = 'ch space';
span.innerHTML = ' ';
} else {
span.className = 'ch';
span.textContent = char;
}
// 文字順に遅延を増やして波打つように出現
span.style.setProperty('--d', (i * 0.05) + 's');
title.appendChild(span);
});
}
// アニメ再生(再ビルドで最初から)
function play() { build(); }
play();
if (btn) btn.addEventListener('click', play);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「文字ごと出現アニメ」の効果を追加してください。
# 追加してほしい効果
文字ごと出現アニメ(タイポグラフィ)
JSでテキストを1文字ずつspan化し、遅延を付けて波打つように出現させます。ページロード時の見出し演出に向いています。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<main class="stage">
<p class="kicker">CHAR REVEAL</p>
<!-- data-text の文字をJSで1文字ずつ<span>化する -->
<h1 class="reveal" data-text="Hello, World"></h1>
<button class="replay" type="button">もう一度再生</button>
</main>
【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Georgia", "Times New Roman", serif;
background:
radial-gradient(900px 400px at 80% 0%, #2d1b4e 0%, transparent 55%),
radial-gradient(900px 400px at 10% 100%, #1b3a4e 0%, transparent 55%),
#0c0c14;
color: #f5f0ff;
overflow: hidden;
}
.stage { text-align: center; padding: 24px; }
.kicker {
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 12px;
letter-spacing: 0.5em;
color: #8a7fb8;
margin-bottom: 18px;
padding-left: 0.5em;
}
.reveal {
font-size: clamp(40px, 10vw, 86px);
font-weight: 700;
line-height: 1.1;
letter-spacing: 0.01em;
/* 折り返しの空白を保持 */
white-space: pre-wrap;
}
/* JSが生成する各文字span。初期は下から透明 */
.reveal .ch {
display: inline-block;
opacity: 0;
transform: translateY(0.5em) rotate(8deg);
/* --d はJSが文字ごとに設定する遅延 */
animation: pop 0.6s cubic-bezier(.2, .8, .2, 1) forwards;
animation-delay: var(--d, 0s);
}
.reveal .space { width: 0.3em; } /* 半角スペース用の幅 */
@keyframes pop {
to { opacity: 1; transform: translateY(0) rotate(0); }
}
.replay {
margin-top: 30px;
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 13px;
color: #f5f0ff;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.18);
padding: 9px 20px;
border-radius: 999px;
cursor: pointer;
transition: background .2s, transform .1s;
}
.replay:hover { background: rgba(255,255,255,0.14); }
.replay:active { transform: scale(0.96); }
@media (prefers-reduced-motion: reduce) {
.reveal .ch { animation-duration: 0.01s; }
}
【JavaScript】
// 文字ごとに出現アニメさせる
(function () {
const title = document.querySelector('.reveal');
const btn = document.querySelector('.replay');
if (!title) return; // null安全
const text = title.dataset.text || '';
// テキストを1文字ずつ<span>に分解して描画
function build() {
title.innerHTML = '';
[...text].forEach((char, i) => {
const span = document.createElement('span');
if (char === ' ') {
span.className = 'ch space';
span.innerHTML = ' ';
} else {
span.className = 'ch';
span.textContent = char;
}
// 文字順に遅延を増やして波打つように出現
span.style.setProperty('--d', (i * 0.05) + 's');
title.appendChild(span);
});
}
// アニメ再生(再ビルドで最初から)
function play() { build(); }
play();
if (btn) btn.addEventListener('click', play);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。