【Next.js】テキストを順番に表示するアニメーションの作り方を解説

公開日: 2026年9月12日 · 読了目安 10 分

Next.js で見出しのテキストを行や単語ごとに順番に表示する reveal アニメーションの作り方。CSS の animation-delay、Framer Motion での単語分割、行のマスク表示、日本語の区切り方、SEO と読み上げへの配慮までコード付きで解説します。

テキストアニメーションは行か単語で区切って出す

Next.js で作った LP のヒーローで、キャッチコピーを順番にふわっと出したい。参考サイトのように1文字ずつ出してみたら読みにくく、スマホで折り返しも崩れた。

この記事は、そんな状況から始める人に向けています。

テキストが順に出るアニメーション(テキスト reveal)は、行か単語までで区切るのが答えです。

1文字ずつ出すと、コピーが読み終わるまで時間がかかります。読み上げソフトにも不親切です。

LP のヒーローなら、ラベル → 見出し → 本文の3段で順番に出すだけで十分に動きが伝わります。

この記事で分かること:

  • CSS の遅延指定(animation-delay)だけで、ブロックごとに順番に出す方法

  • Motion(旧 Framer Motion)で単語・文節ごとに出すコンポーネント

  • 行を下からマスクで出す、ヒーロー向けの見せ方

  • 日本語の見出しをどこで区切るか

  • SEO・読み上げ・動きを減らす設定への配慮

1文字ずつのテキストアニメーションが読みにくい理由

文字単位の演出は、映像のオープニングのように「見せる」ためのものです。LP の見出しは「読ませる」ためにあるので、相性がよくありません。

具体的には、次の3つの問題が起きます。

  • 見出しが揃うまで内容が分からず、その前にスクロールされてしまう

  • 1文字ずつ絶対配置したり幅を固定したりすると、画面幅を変えたときに折り返しが崩れる

  • 読み上げソフトが1文字ずつ区切って読むことがあり、意味が通らなくなる

行や単語で区切れば、どれも起きにくくなります。全体の時間も短く済みます。

目安として、ファーストビューのテキストは1秒以内に全部出し終えるようにします。

CSS だけでテキストをブロックごとに順番に出す

最初に試したいのは、単語に分けずにブロックごとに遅らせる方法です。

ラベル・h1・本文・ボタンの4つを、0.12 秒ずつずらして下から出します。多くの LP は、これだけで十分に動きのある見た目になります。

JavaScript を使わないので、サーバーコンポーネントのままで動きます。まず globals.css に keyframes を1つ足します。

app/globals.css
@keyframes reveal-up {
    from {
      opacity: 0;
      transform: translateY(12px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

Tailwind CSS の任意値クラスで遅延をずらす

animate-[reveal-up_0.6s_ease-out_0.12s_both] は、CSS の動きの指定を1つのクラスにまとめたものです。中身は次のとおりです。

  • reveal-up:さきほど足した動きの名前

  • 0.6s:動く時間

  • ease-out:最後をゆっくり止める動き方

  • 0.12s:動き始めるまでの遅れ

  • both:動く前と後の見た目を保つ指定

最後の both があると、遅延の間も透明のまま待ちます。終わった後は、表示されたまま残ります。

components/HeroText.tsx
export function HeroText() {
    return (
      <div className="mx-auto max-w-3xl px-6 pt-32 text-center">
        <p className="animate-[reveal-up_0.6s_ease-out_both] text-sm font-medium text-indigo-600 motion-reduce:animate-none">
          春の新機能を追加しました
        </p>
        <h1 className="mt-4 animate-[reveal-up_0.6s_ease-out_0.12s_both] text-4xl font-semibold tracking-tight motion-reduce:animate-none md:text-6xl">
          動きのある LP を、今日のうちに公開する
        </h1>
        <p className="mt-6 animate-[reveal-up_0.6s_ease-out_0.24s_both] text-lg text-zinc-600 motion-reduce:animate-none">
          セクションを選んで貼るだけ。文言を差し替えれば完成です。
        </p>
        <a
          href="#start"
          className="mt-10 inline-block animate-[reveal-up_0.6s_ease-out_0.36s_both] rounded-full bg-zinc-900 px-6 py-3 text-white motion-reduce:animate-none"
        >
          無料で始める
        </a>
      </div>
    );
  }

ブロックごとのテキスト表示が向いている場面

見出しが2行以上ある、または文言を頻繁に変えるときに向いています。単語の分け方を気にしなくてよいので、文章を差し替えても壊れません。

Framer Motion でテキストを単語・文節ごとに出す

見出しそのものに動きを持たせたいときは、テキストを単語や文節に分け、それぞれを span で包んで遅延をずらします。Motion を使うと、遅延と動きを1か所で決められます。

次のコンポーネントは、分けた文字列の配列を受け取ります。英語なら空白で分け、日本語は自分で文節に分けて渡します。

components/SplitReveal.tsx
"use client";

  import { Fragment } from "react";
  import { motion, useReducedMotion } from "motion/react";

  export function SplitReveal({
    parts,
    spaced = false,
    className,
  }: {
    parts: string[];
    spaced?: boolean;
    className?: string;
  }) {
    const reduce = useReducedMotion();

    return (
      <h1 className={className}>
        {parts.map((part, i) => (
          <Fragment key={i}>
            <motion.span
              className="inline-block"
              initial={reduce ? false : { opacity: 0, y: 12 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ duration: 0.5, delay: i * 0.06, ease: "easeOut" }}
            >
              {part}
            </motion.span>
            {spaced && i < parts.length - 1 ? " " : null}
          </Fragment>
        ))}
      </h1>
    );
  }

日本語のテキストは文節か読点で区切る

日本語には単語の間に空白がありません。そのため split(" ") は使えず、1文字ずつに分けるのも避けます。

見出しを文節(「動きのある」「LP を」のような意味のかたまり)か、読点のところで手で分けて配列にします。

span に inline-block を付けているので、折り返しは区切りの位置でだけ起きます。文節で区切ると、スマホで単語の途中で改行されることも防げます。

読み上げで区切られるのが気になる場合は、h1 に aria-label で全文を付け、分けた span には aria-hidden を付けます。

app/page.tsx(使い方)
// 日本語: 文節ごとに手で区切る
  <SplitReveal
    parts={["動きのある", "LP を、", "今日のうちに", "公開する"]}
    className="text-4xl font-semibold tracking-tight md:text-6xl"
  />

  // 英語: 空白で区切り、spaced で単語の間に空白を戻す
  <SplitReveal
    parts={"Ship a landing page that moves".split(" ")}
    spaced
    className="text-4xl font-semibold tracking-tight md:text-6xl"
  />

単語ごとの遅延は 0.06 秒前後にする

6つに区切った見出しなら、0.06 秒ずつで最後の文節は 0.3 秒後に出始めます。0.15 秒ずつにすると、全部出るまで1秒を超え、待たされている感じが出ます。

区切りが多いときほど、1つあたりの遅延を短くします。

テキストを行ごとに下からマスクで出す方法

大きな見出しで、行ごとに下から「せり上がる」ように出す演出もよく使われます。

仕組みは、行ごとにはみ出しを隠す箱(overflow-hidden)を作り、中の文字を箱の外(下)から元の位置へ動かすだけです。箱の外にある間は見えないので、マスクで切り抜いたように見えます。

スクロールした先のセクション見出しにも使えるよう、whileInView で画面に入ったときに再生します。

components/LineMaskReveal.tsx
"use client";

  import { motion } from "motion/react";

  export function LineMaskReveal({ lines }: { lines: string[] }) {
    return (
      <h2 className="text-4xl font-semibold leading-tight md:text-6xl">
        {lines.map((line, i) => (
          <span key={i} className="block overflow-hidden pb-1">
            <motion.span
              className="block"
              initial={{ y: "110%" }}
              whileInView={{ y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.7, delay: i * 0.1, ease: [0.22, 1, 0.36, 1] }}
            >
              {line}
            </motion.span>
          </span>
        ))}
      </h2>
    );
  }

マスク表示するテキストの行は短めに決めておく

lines に渡す1行が長いと、スマホでは1つの行がさらに折り返されます。動き自体は壊れませんが、2行がまとめて出てくるため演出が弱まります。

スマホでも1行に収まる長さで区切ると、意図どおりに見えます。

pb-1 は、g や y のように下に伸びる文字が箱で切れないための余白です。

テキストアニメーションの SEO と読み上げへの配慮

テキストは最初から HTML に入れる

この記事のコードは、どれも見出しの文字がサーバーで出力される HTML に含まれます。Motion の initial で透明にしていても、文字そのものは HTML にあるので、検索エンジンにも読み上げソフトにも届きます。

避けたいのは、useEffect の中で innerText を1文字ずつ足していく作り方です。HTML の段階では見出しが空になり、内容が伝わりにくくなります。

動きを減らす設定では最初から全文を出す

端末の設定で動きを減らしている人には、最初から全文を表示しています。

CSS の方法では motion-reduce:animate-none を使います。SplitReveal では useReducedMotion(動きを減らす設定を読み取る仕組み)を使っています。

サイト全体でまとめて対応する方法もあります。共通のレイアウトを MotionConfig(動きの設定をまとめる部品)で包み、reducedMotion="user" を指定します。

ファーストビューは1秒以内に出し終える

透明(opacity: 0)の要素は、表示の速さを測る指標(LCP)の対象から外れます。そのため、見出しが見えるまで計測が遅れます。

ファーストビューの見出しは、遅延を含めて1秒以内に出し終えるのが安全です。

テキストが順に出る完成ヒーローを使う方法

DesignLayer の「桜エディトリアルポスター」の詳細ページ。左でライブプレビューを確かめ、右の「Cursor 用にコピー」でソースをコピーできる
DesignLayer の「桜エディトリアルポスター」の詳細ページ。左でライブプレビューを確かめ、右の「Cursor 用にコピー」でソースをコピーできる

行のマスク、文字を切り抜いた見出し、背景の動きと合わせたタイミング。ここまで揃ったヒーローは、この記事のコードを組み合わせても調整に時間がかかります。

DesignLayer は、人が組んだ React + Tailwind のモーション付きセクションを、ブラウザでライブプレビューしてからコピーできるカタログです。

閲覧はアカウントもカードも不要です。https://design-layer.com/ja/components

無料サンプル10個を無料アカウントで回数制限なくコピーでき、動画プロンプトは月1回です。Free では MCP に接続できません。カタログ全体は30日ビルドパス($19の1回払い、自動更新なし)、Lifetime Access(先着100名の$79の1回払い)、Plus(月額 $9、いつでも解約可)です。無料サンプルなら回数の上限なくコピーできます。それ以外は30日ビルドパス($19)、Lifetime($79)、Plus(月額 $9)です。

ヒーロー全体の作り方は https://design-layer.com/ja/articles/nextjs-animated-hero-section-guide で解説しています。

テキストアニメーションについてよくある質問

  • 見出しの文字が最初から HTML にあれば、問題になりにくいです。この記事の方法はすべてその形です。

    ブラウザ側で後から文字を書き足す作り方は避けてください。

まとめ:Next.js でテキストを順番に表示する手順

  • テキストは行か単語(日本語は文節)で区切って出し、1文字ずつは避ける

  • まずは CSS の遅延指定で、ラベル・見出し・本文を順番に出す

  • 見出しそのものを動かすなら、Motion で区切りごとに 0.06 秒ずつ遅らせる

  • 行のマスク表示は、はみ出しを隠す箱と縦の移動で作れる

  • 文字は最初から HTML に入れ、動きを減らす設定では全文をすぐに出す

まずは CSS の HeroText をそのまま貼り、遅延の値を変えて好みのテンポを探してください。

Motion の基本は https://design-layer.com/ja/articles/nextjs-framer-motion-guide 、ヒーロー全体の組み方は https://design-layer.com/ja/articles/nextjs-animated-hero-section-guide が参考になります。

関連ガイド