文字ごと出現アニメ

JSでテキストを1文字ずつspan化し、遅延を付けて波打つように出現させます。ページロード時の見出し演出に向いています。

#js#css#animation

ライブデモ

使用例(お題: カフェ 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 = '&nbsp;';
      } 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 = '&nbsp;';
      } 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 = '&nbsp;';
      } 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。