【Next.js】Tailwind CSS でアニメーションを付ける方法を解説|入場・ホバー・ループ
公開日: 2026年9月12日 · 読了目安 13 分
Next.js と Tailwind CSS だけで、ホバー、ページを開いたときの入場、ロゴのループ、画面に入ったときのフェードを付ける方法をコード付きで解説。v4 の @theme と v3 の設定、Framer Motion が必要になる境目、よくある失敗もまとめます。
Next.js のアニメーションは Tailwind CSS だけで足りる
Next.js で LP を作っていて、静的なページに少し動きを足したい。けれど、アニメーションのためにライブラリを入れるべきか迷う。
そんなときは、まず Tailwind CSS だけで書いてください。ホバーやページを開いたときの入場、ロゴのループは、CSS のアニメーションで十分に作れます。
Motion(旧 Framer Motion)を足すのは、スクロール量に合わせて値を変えたいときや、要素が消えるときの動きが必要なときだけです。境目はこの記事の後半で整理します。
この記事で分かることは次のとおりです。
transition と hover: で作るホバーの動き
@keyframes を足して作る、ページを開いたときの入場アニメーション
ロゴやテキストが流れ続けるループ
画面に入ったときにフェードさせる、ブラウザで動く小さな部品
Tailwind CSS だけでは届かない動きと、よくある失敗
Tailwind CSS でアニメーションを書く3つの利点
1つ目は、サーバーコンポーネント(サーバーで組み立てる部品)のまま使えることです。Tailwind CSS のクラスはただの CSS なので、"use client"(この部品はブラウザで動かす、という印)を付けなくても動きます。
ページ全体を、ブラウザで動く部品にしなくて済みます。
2つ目は、JavaScript が増えないことです。動きはブラウザの CSS エンジンが処理します。
transform と opacity だけを動かせば、スマホでも滑らかに動きやすくなります。
3つ目は、変更が1か所で済むことです。イージングや時間をテーマに登録しておけば、ページ全体の動きを同じ調子に揃えられます。
Tailwind CSS でホバーアニメーションを作る
ホバーは一番手軽な動きです。transition で変化にかける時間を決め、hover: で変化後の見た目を書きます。
親に group を付けると、カードにマウスを乗せたときに中の矢印だけを動かす、といった指定もできます。
動かすものは1つか2つに絞ります。候補は translate と scale、opacity、色です。
box-shadow を大きく変える動きは描画が重く、カードが多いと引っかかって見えます。
export function FeatureCard({ title, body }: { title: string; body: string }) {
return (
<article className="group rounded-2xl border border-zinc-200 p-6 transition duration-200 ease-out hover:-translate-y-1 hover:border-zinc-400">
<h3 className="font-semibold">{title}</h3>
<p className="mt-2 text-sm text-zinc-600">{body}</p>
<span className="mt-4 inline-block text-sm transition-transform duration-200 group-hover:translate-x-1">
詳しく見る →
</span>
</article>
);
}Tailwind CSS の入場アニメーションを @keyframes で作る
Tailwind CSS には animate-spin や animate-bounce などが最初から入っていますが、LP の入場には向きません。下から少し浮き上がって現れる動きを、自分で1つだけ登録します。
Tailwind CSS v4:CSS ファイルに入場の動きを登録する
v4 では、globals.css の @theme(共通の値をまとめる場所)に書きます。
その中に、--animate- で始まる名前と @keyframes(動きの中身)を並べます。
すると、animate-rise のようなクラスが使えるようになります。
下の例では、後で使うループ用の marquee も一緒に登録しています。
/* app/globals.css */
@import "tailwindcss";
@theme {
--animate-rise: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
--animate-marquee: marquee 30s linear infinite;
@keyframes rise {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
}Tailwind CSS v3:設定ファイルに入場の動きを登録する
v3 では、tailwind.config.ts の theme.extend に同じ内容を書きます。
書く項目は keyframes と animation の2つです。
クラス名は v4 と同じ animate-rise になります。
// tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
theme: {
extend: {
keyframes: {
rise: {
from: { opacity: "0", transform: "translateY(16px)" },
to: { opacity: "1", transform: "translateY(0)" },
},
marquee: {
from: { transform: "translateX(0)" },
to: { transform: "translateX(-50%)" },
},
},
animation: {
rise: "rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both",
marquee: "marquee 30s linear infinite",
},
},
},
} satisfies Config;ヒーローの見出し・説明・ボタンを順番にアニメーションさせる
要素ごとに開始を少しずらすと、ページを開いた瞬間に読む順番が生まれます。
motion-safe:[animation-delay:100ms] のような任意プロパティで遅らせます。間隔は 80〜120ms が目安です。
animate-rise はショートハンドなので、遅延にも motion-safe: を付けて後から上書きされないようにします。
motion-safe: を付けておくと、OS の設定で動きを減らしている人には動きを出しません。この部品は、サーバーで組み立てる部品のままです。
export function Hero() {
return (
<section className="px-6 py-32 text-center">
<h1 className="text-5xl font-semibold tracking-tight motion-safe:animate-rise">
LP の公開を、今週中に。
</h1>
<p className="mx-auto mt-6 max-w-xl text-zinc-600 motion-safe:animate-rise motion-safe:[animation-delay:100ms]">
Next.js と Tailwind CSS で作る、軽い入場アニメーションの例です。
</p>
<a
href="#pricing"
className="mt-10 inline-flex rounded-full bg-zinc-950 px-6 py-3 text-sm text-white motion-safe:animate-rise motion-safe:[animation-delay:200ms]"
>
料金を見る
</a>
</section>
);
}Tailwind CSS でロゴが流れるループアニメーションを作る
導入企業のロゴを横に流すマーキーも、CSS だけで作れます。同じリストを2回並べ、全体を -50% まで動かして、最初に戻ったことが見えないようにします。
継ぎ目をずらさないため、要素の間隔は gap ではなく各要素の px で取ります。
マウスを乗せたら止まるようにしておくと、ロゴを読みたい人に親切です。複製した側は aria-hidden にして、読み上げが2回にならないようにします。
const logos = ["Acme", "Globex", "Initech", "Umbrella", "Hooli"];
export function LogoMarquee() {
return (
<div className="overflow-hidden py-10">
<ul className="flex w-max motion-safe:animate-marquee hover:[animation-play-state:paused]">
{[...logos, ...logos].map((name, i) => (
<li
key={i}
aria-hidden={i >= logos.length}
className="px-8 text-lg font-semibold text-zinc-400"
>
{name}
</li>
))}
</ul>
</div>
);
}ループアニメーションは1ページに1つまで
ずっと動き続ける要素は、視線を奪い続けます。ロゴのマーキーとグラデーションの背景が同じ画面にあると、どちらも目立たなくなります。
ループは1ページに1つ、duration は長め(20〜40秒)にします。
詳しい作り方は https://design-layer.com/ja/articles/nextjs-logo-marquee-guide で解説しています。
画面に入ったときのフェードアニメーションを付ける
ここまでの入場は、ページを開いた瞬間に動きます。スクロールして画面に入ったときに動かしたいなら、要素が見えたかどうかを知る必要があり、CSS のクラスだけでは足りません。
Motion を入れなくても、ブラウザで動く小さな部品で対応できます。使うのは IntersectionObserver(画面に入ったかを調べる仕組み)です。
ブラウザで動かす印は、このファイルだけに付けます。ページはサーバーで組み立てる部品のままにして、中身を Reveal で包みます。
"use client";
import { useEffect, useRef, useState, type ReactNode } from "react";
export function Reveal({ children, className = "" }: { children: ReactNode; className?: string }) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
{ rootMargin: "0px 0px -10% 0px" }
);
observer.observe(el);
return () => observer.disconnect();
}, []);
return (
<div
ref={ref}
className={
(visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0") +
" transition duration-700 ease-out motion-reduce:translate-y-0 motion-reduce:opacity-100 motion-reduce:transition-none " +
className
}
>
{children}
</div>
);
}CSS だけでスクロールアニメーションを付ける方法
CSS には、スクロール位置を時間の代わりに使う animation-timeline: view() という仕組みもあります。JavaScript が要らないのが利点です。Chrome と Safari は対応していますが、Firefox はまだ標準では動きません。
LP で使うなら、未対応のブラウザでは最初から表示されるように書いておきます。
Tailwind CSS だけでは難しいアニメーション
次のような動きは、CSS のクラスだけで書くと複雑になります。ここから先は Motion(motion パッケージ)の出番です。
スクロール量に合わせて、位置や大きさが少しずつ変わる動き
セクションを画面に固定し、スクロールで中身を切り替える動き
ドラッグやスワイプに合わせた動き
要素が消えるときのアニメーションや、レイアウトの変化をなめらかにつなぐ動き
導入と使い分けは https://design-layer.com/ja/articles/nextjs-framer-motion-guide にまとめています。Tailwind CSS で作った入場やホバーは、Motion を入れた後もそのまま残してかまいません。
Tailwind CSS のアニメーションでよくある失敗
1つ目は、クラス名を文字列でつなげて作ることです。
Tailwind CSS はソースに書かれたクラス名をそのまま探します。そのため "animate-" + name のように組み立てたクラスは、CSS が生成されません。
使うクラスは "animate-rise" のように、省略せずに書きます。
2つ目は、width や height、box-shadow を動かすことです。レイアウトの再計算や重い再描画が起き、カクつきやすくなります。
動かすのは transform と opacity を基本にします。
3つ目は、全セクションに入場を付けることです。どれも同じ強さで動くと、ページの山場が消えます。
強い入場はヒーローと CTA だけにし、他は Reveal の薄いフェードか、動きなしにします。
4つ目は、動きを減らす OS の設定への配慮を忘れることです。ループや入場には motion-safe: を付け、動きを減らしたい人には止まった状態で見せます。
完成したアニメーション付きセクションをコピーして使う方法

ホバーや入場は、この記事のコードで十分に作れます。
一方で、イージングと遅延の組み合わせを詰める作業は時間がかかります。Motion でスクロール連動の動きを一から作ると、1セクションで半日かかることもあります。
その場合は、ブラウザで実際の動きを見てから、完成したセクションを自分の Next.js に載せる方法があります。
DesignLayer は、人が組んだ React + Tailwind CSS のモーション付きセクションを、ライブプレビューで確かめてからコピーできるカタログです。
閲覧にカードは要りません。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)です。
Tailwind CSS アニメーションについてよくある質問
クラスで付けるホバー、入場、ループには不要です。画面に入ったことを調べるときや、state(部品が覚えておく値)で表示を切り替えるときだけ、その部品のファイルに "use client" を付けます。
まとめ:Next.js で Tailwind CSS アニメーションを付ける手順
ホバーと入場、ループは Tailwind CSS だけで作れる
入場とループの動きは、テーマの設定に1つずつ登録する
画面に入ったときの動きは、ブラウザで動く小さな部品で足りる
スクロール連動やドラッグ、消えるときの動きが要るときだけ Motion を足す
強い動きはヒーローと CTA に絞り、motion-safe: で動きを減らしたい人に配慮する
まずはヒーローの見出しとボタンに animate-rise を付け、遅延を 100ms ずつずらしてみてください。スクロールで要素を出す演出をもっと詰めたい場合は https://design-layer.com/ja/articles/nextjs-scroll-reveal-guide も参考になります。


