



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp









「ボタンを押したわけでもないのに、スクロールするだけで要素がふわっと現れる」——あのアニメーション、気になったことはありませんか?あれが CSSフェードイン です。実装の仕方によって「ページを開いた瞬間に表示される」パターンと「スクロールして画面に入ったら現れる」パターンの2種類があり、それぞれ用途が異なります。
CSSフェードインは opacity(透明度)を 0 から 1 に変化させることで実現します。たった数行のコードで視覚的な滑らかさが生まれ、ページ全体の印象がプロっぽくなります。最近ではJavaScriptをほぼ書かずに実現できる方法も増えており、初心者でも取り組みやすい技術になってきました。
この記事では基本的な @keyframes アニメーションから、スクロール連動、方向付きアニメーション、時間差で複数要素を表示する方法、さらにCSSだけで実現する最新手法まで順を追って解説します。コードをコピーしてそのまま使えるように設計しているので、実際のプロジェクトにすぐ活かせます。
まずはこの記事で学べることをまとめておきます。
@keyframes や transition を使って、ページを開いた瞬間に要素がフェードインする方法ページを開いた瞬間に要素がふわっと表示されるパターンです。最もシンプルな方法は @keyframes を使ってアニメーションを定義し、対象要素に animation プロパティで適用することです。JavaScriptは一切不要で、CSSだけで完結します。
opacity は要素の透明度を制御するプロパティです。0 が完全に透明、1 が完全に不透明になります。@keyframes の from(開始状態)に opacity: 0、to(終了状態)に opacity: 1 を指定することで、透明から不透明への変化が生まれます。
/* フェードインアニメーションの定義 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 要素に適用 */
.fade-in {
animation: fadeIn 1s ease forwards;
}animation プロパティには「アニメーション名・再生時間・イージング・終了後の状態」をまとめて指定しています。forwards は「アニメーション終了後も最後の状態(opacity: 1)を維持する」という意味です。これを忘れると、アニメーション完了後に要素が消えてしまうので注意しましょう。
/* 遅延ありフェードイン */
.fade-in-delay {
opacity: 0; /* 初期状態は透明 */
animation: fadeIn 1s ease 0.5s forwards;
/* アニメーション名 / 時間 / イージング / 遅延 / 終了後の状態 */
}animation の4番目の値が遅延時間です。ここでは 0.5s(0.5秒後)に再生が始まります。遅延中は初期状態(opacity: 0)のままになるので、要素に opacity: 0 を初期値として指定しておくことが大切です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>フェードインサンプル</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 class="fade-in">タイトルがふわっと表示される</h1>
<p class="fade-in-delay">少し遅れて段落がフェードイン</p>
</body>
</html>この構成がCSSフェードインの最小構成です。fade-in クラスを付けた要素はページ読込と同時にアニメーションが始まり、fade-in-delay クラスの要素は0.5秒遅れてフェードインします。
フェードインの「なめらかさ」はイージングで決まります。ease-out(最初は速く、最後は遅く)が最もナチュラルに見えるため、初心者には ease または ease-out から始めることをおすすめします。
/* イージング比較 */
.ease-linear { animation: fadeIn 1s linear forwards; }
.ease-ease { animation: fadeIn 1s ease forwards; }
.ease-ease-in { animation: fadeIn 1s ease-in forwards; }
.ease-ease-out { animation: fadeIn 1s ease-out forwards; }
.ease-cubic { animation: fadeIn 1s cubic-bezier(0.4, 0, 0.2, 1) forwards; }| 値 | 動き | おすすめ用途 |
|---|---|---|
| linear | 一定速度で変化 | ローダーなど機械的な動き |
| ease | 最初と最後が少しゆっくり(デフォルト) | 汎用的なフェードイン |
| ease-in | 最初ゆっくり、最後速い | 要素が消えるとき |
| ease-out | 最初速く、最後ゆっくり | 要素が現れるとき(最もナチュラル) |
| cubic-bezier | 自由に調整 | デザイン重視の凝った演出 |
アニメーション時間は 0.6s〜1.2s が使いやすいバランスです。速すぎると気づかれず、遅すぎるとユーザーにストレスを与えます。本文要素なら 0.8s 程度を基準に微調整しましょう。
@keyframes 以外のアプローチとして、transition プロパティを使う方法があります。こちらは「状態変化のとき自動でアニメーションを補間する」仕組みです。要素の初期状態を opacity: 0 にしておき、JavaScriptでクラスを付与するだけで滑らかにフェードインします。
@keyframes との違いは「トリガーを自分でコントロールしやすい」点にあります。ボタンクリック時・メニューが開いたとき・データ取得完了後など、任意のタイミングで発火できます。スクロール連動には別途Intersection Observerが必要ですが(Section 4で解説)、クリックや状態変化のトリガーには transition が最適です。
/* 初期状態:透明 */
.fade-target {
opacity: 0;
transition: opacity 0.8s ease-out;
}
/* クラスを追加すると表示 */
.fade-target.is-visible {
opacity: 1;
}// ページ読込後にクラスを付与してフェードイン
window.addEventListener('load', () => {
document.querySelector('.fade-target').classList.add('is-visible');
});CSS側では opacity: 0 を初期値として設定し、transition で変化の時間とイージングを指定します。JavaScriptは classList.add('is-visible') を呼ぶだけ。これだけで opacity が 0 → 1 にアニメーションします。
ページ内の複数要素を一括フェードインさせたい場合は、親要素(body など)にクラスを付与し、CSSセレクタで子孫要素をまとめて制御する方法が便利です。
/* ページ読込直後に全要素を一括フェードイン */
.fade-target {
opacity: 0;
transition: opacity 0.8s ease-out;
}
/* body に loaded クラスが付いたとき子孫すべて表示 */
body.loaded .fade-target {
opacity: 1;
}window.addEventListener('load', () => {
document.body.classList.add('loaded');
});この設計のメリットは、HTML上で .fade-target クラスを付けるだけで自動的にフェードインの対象になる点です。JavaScriptを書き直す必要がなく、デザイナーとエンジニアで作業を分担しやすくなります。
ユーザー操作への応答として transition を使う例として、ホバー時に不透明度を変化させる表現も有効です。
/* リンクホバー時のフェードイン(不透明度変化) */
.hover-fade {
opacity: 0.6;
transition: opacity 0.3s ease;
}
.hover-fade:hover {
opacity: 1;
}この場合はJavaScriptすら不要です。:hover という状態変化に transition が自動で反応するため、純粋なCSSだけで完結します。
| 手法 | トリガー | JS必要? | おすすめ場面 |
|---|---|---|---|
| @keyframes | ページ読込・自動 | 不要 | ページ開いた瞬間のイントロ演出 |
| transition + class付与 | 任意のイベント | 数行必要 | クリック・状態変化・スクロール連動 |
| transition + :hover | マウスオーバー | 不要 | ボタン・リンクの視覚フィードバック |
「スクロールして要素が画面に入ってきたタイミングでフェードイン」という表現は、現代のウェブサイトで最も多く使われるパターンのひとつです。以前は scroll イベントを使ってスクロール量を計算する方法が主流でしたが、パフォーマンスの問題から現在は Intersection Observer API を使うのが王道です。
Intersection Observer は「要素がビューポート(画面の見える領域)に入ったか出たか」を効率よく検知するブラウザ標準のAPIです。scroll イベントと違い、毎フレーム実行されるわけではなくブラウザが最適なタイミングで呼び出すため、重い処理になりません。2024年時点で主要ブラウザのサポート率はほぼ100%です。
3ファイルを以下のように作成します。HTMLで対象要素を用意し、CSSで初期状態(透明)を定義し、JavaScriptで観察を開始します。
まずHTML:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>スクロールフェードイン</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="scroll-fade">最初のカード</div>
<div class="scroll-fade">2番目のカード</div>
<div class="scroll-fade">3番目のカード</div>
<script src="script.js"></script>
</body>
</html>次にCSS(style.css):
/* 初期状態:非表示(透明 + わずかに下にずれた位置) */
.scroll-fade {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.7s ease-out, transform 0.7s ease-out;
}
/* 表示状態:クラス付与で発動 */
.scroll-fade.is-visible {
opacity: 1;
transform: translateY(0);
}そしてJavaScript(script.js):
// IntersectionObserver でスクロール検知
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// 要素が画面内に入ったとき
if (entry.isIntersecting) {
// 表示クラスを付与してフェードイン
entry.target.classList.add('is-visible');
// 一度表示したら監視を解除(パフォーマンス最適化)
observer.unobserve(entry.target);
}
});
},
{
// 要素が10%以上見えたら発火
threshold: 0.1,
// 画面下から100px手前で発火(早めのタイミング)
rootMargin: '0px 0px -100px 0px',
}
);
// .scroll-fade クラスを持つ全要素を監視対象に登録
document.querySelectorAll('.scroll-fade').forEach((el) => {
observer.observe(el);
});ここでは opacity だけでなく transform: translateY(20px)(20px下にずれた位置)も初期値に加えています。下からふわっと浮き上がる動きになり、単純な透明度変化より視覚的に豊かな演出になります。
IntersectionObserver コンストラクタには「コールバック関数」と「オプション」を渡します。コールバックの引数 entries は監視している要素の配列です。entry.isIntersecting が true になったとき、その要素が画面内に入ったことを意味します。
threshold: 0.1:要素の10%が見えたらコールバックを呼ぶ。0にすると1pxでも見えた瞬間に発火rootMargin: '0px 0px -100px 0px':画面下端から100px手前に「仮の境界線」を引く。実際に画面に入る少し前に発火させることで、タイミングを早めに調整できるobserver.unobserve(entry.target):一度フェードインしたら監視を解除。同じ要素が再び検知されることを防ぎ、余分な処理を削減フェードインさせたい要素すべてに scroll-fade クラスをつけるだけで適用できます。JavaScriptは一切追加しなくてOKです。querySelectorAll('.scroll-fade') がまとめて全要素を監視対象に登録してくれます。
注意点として、display: none になっている要素はIntersection Observerに検知されません。opacity: 0 で非表示にして visibility や display は変更しない設計にしましょう。
「ただ透明度が変わるだけ」より「下からふわっと浮き上がる」「右からスライドしてくる」ような動きのほうが、視覚的にメリハリがあります。これを実現するのが transform プロパティとの組み合わせです。opacity と同時に translateX / translateY(位置のずれ)もアニメーションさせることで、方向のある動きを作れます。
ポイントは「初期値に位置のずれを持たせ、表示時に translate(0, 0) に戻す」ことです。フェードアウト先を定義するのではなく、フェードイン元を定義するイメージです。
| クラス名 | 初期値(transform) | 動きの方向 | 用途例 |
|---|---|---|---|
| .fade-up | translateY(40px) | 下から上へ浮き上がる | 本文テキスト・カード |
| .fade-down | translateY(-40px) | 上から下へ落ちてくる | ナビゲーション・ヘッダー |
| .fade-left | translateX(40px) | 右から左へスライド | 右サイドバー・画像 |
| .fade-right | translateX(-40px) | 左から右へスライド | 左サイドバー・見出し |
/* 共通トランジション設定 */
.fade-up,
.fade-down,
.fade-left,
.fade-right {
opacity: 0;
transition: opacity 0.7s ease-out, transform 0.7s ease-out;
}
/* 方向別の初期位置 */
.fade-up { transform: translateY(40px); } /* 下から上へ */
.fade-down { transform: translateY(-40px); } /* 上から下へ */
.fade-left { transform: translateX(40px); } /* 右から左へ */
.fade-right { transform: translateX(-40px); } /* 左から右へ */
/* 表示状態(Intersection Observer でクラス付与) */
.fade-up.is-visible,
.fade-down.is-visible,
.fade-left.is-visible,
.fade-right.is-visible {
opacity: 1;
transform: translate(0, 0);
}<div class="fade-up">下から上へフェードイン</div>
<div class="fade-left">右から左へフェードイン</div>
<div class="fade-right">左から右へフェードイン</div>
<div class="fade-down">上から下へフェードイン</div>// 全方向フェードイン要素を一括監視
const targets = document.querySelectorAll(
'.fade-up, .fade-down, .fade-left, .fade-right'
);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -80px 0px' }
);
targets.forEach((el) => observer.observe(el));querySelectorAll のセレクタをカンマ区切りで書くと、すべての方向クラスをまとめて監視対象にできます。JavaScript側の処理は方向に関係なく同じなので、コードがシンプルに保てます。
移動量(ここでは 40px)は画面サイズや要素の大きさによって調整しましょう。モバイル向けは 20〜30px、デスクトップの大きな要素には 50〜60px 程度が自然に見えます。大きすぎると「飛んでくる感じ」になり、安っぽい印象になります。
上下と左右を同時に指定することで、斜めの動きも作れます。たとえば左下から右上へフェードインするなら初期値を transform: translate(-30px, 30px) にして、表示時に transform: translate(0, 0) にします。
「リストの各項目が順番にふわっと現れる」「カードが一枚ずつタイミングをずらして表示される」——これを stagger(スタッガー)アニメーション と呼びます。全要素が一斉に表示されるより、時間差をつけたほうがリズムが生まれ、情報が読み取りやすくなります。
CSSで時間差を作る方法は主に2つあります。① nth-child で各要素に異なる transition-delay を指定する方法と、② CSSカスタムプロパティ(変数)と calc() を組み合わせてHTML側で遅延の係数を渡す方法です。
<ul class="stagger-list">
<li>リスト項目1</li>
<li>リスト項目2</li>
<li>リスト項目3</li>
<li>リスト項目4</li>
<li>リスト項目5</li>
</ul>/* 共通設定:非表示 */
.stagger-list li {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}
/* nth-child で各要素に異なる遅延を指定 */
.stagger-list.is-visible li:nth-child(1) { transition-delay: 0s; }
.stagger-list.is-visible li:nth-child(2) { transition-delay: 0.1s; }
.stagger-list.is-visible li:nth-child(3) { transition-delay: 0.2s; }
.stagger-list.is-visible li:nth-child(4) { transition-delay: 0.3s; }
.stagger-list.is-visible li:nth-child(5) { transition-delay: 0.4s; }
/* 表示状態 */
.stagger-list.is-visible li {
opacity: 1;
transform: translateY(0);
}// 親要素を監視してクラスを付与
const list = document.querySelector('.stagger-list');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
observer.observe(list);親要素(.stagger-list)が画面に入ったタイミングで is-visible を付与すると、CSS側で transition-delay が効いて子要素が順番にフェードインします。JavaScriptは親要素を一つ監視するだけでよく、シンプルです。
デメリットは「要素数が増えるとCSSの行数も増える」点です。5〜8項目程度ならこの方法で十分ですが、可変長の場合は次の方法が向いています。
要素数が動的に変わる場合やCSSをすっきり保ちたい場合は、HTML側で --i というインデックス変数を渡し、CSS側で calc() を使って遅延を計算する方法が便利です。
/* CSS カスタムプロパティ + calc() で遅延を自動計算 */
.stagger-card {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out calc(var(--i, 0) * 0.12s),
transform 0.6s ease-out calc(var(--i, 0) * 0.12s);
}
.stagger-card.is-visible {
opacity: 1;
transform: translateY(0);
}<!-- HTML側でCSS変数を渡す -->
<div class="stagger-card" style="--i: 0">カード1</div>
<div class="stagger-card" style="--i: 1">カード2</div>
<div class="stagger-card" style="--i: 2">カード3</div>
<div class="stagger-card" style="--i: 3">カード4</div>// 各カードを個別に監視する場合
const cards = document.querySelectorAll('.stagger-card');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -60px 0px' }
);
cards.forEach((card) => observer.observe(card));--i に 0, 1, 2, 3... を渡すと、それぞれ 0s, 0.12s, 0.24s, 0.36s の遅延が計算されます。CSSを一切変更せずHTML側だけ追加すれば新しい要素が増やせるため、CMSで動的にコンテンツが変わるページに向いています。
| 方法 | 向いている場面 | メリット | デメリット |
|---|---|---|---|
| nth-child + transition-delay | 固定数のリスト | CSSのみで完結(JSは親監視のみ) | 要素数が多いとCSSが増える |
| CSS変数 + calc() | 動的・可変長のリスト | CSS・JS共にシンプル | HTML側に–i追加が必要 |
2024年以降、Chrome・Edge・Firefox の最新バージョンでは Scroll-driven Animations(スクロール駆動アニメーション) という仕様が使えるようになりました。これにより、Intersection Observer すら使わず、純粋なCSSだけでスクロール連動アニメーションが実装できます。
animation-timeline: view() は「要素がビューポートに対してどのくらい見えているか」をアニメーションの時間軸として使う機能です。animation-range でアニメーションを再生する範囲を「要素がビューポートに入り始めた瞬間から何%まで」という形で指定できます。
重要な注意点:2024年時点でSafari(iOS含む)のサポートが完全ではありません。本番環境で使う場合は必ずフォールバックを用意するか、対応ブラウザのシェアを確認したうえで採用を判断してください。
<div class="scroll-animate">スクロールで表示1</div>
<div class="scroll-animate">スクロールで表示2</div>
<div class="scroll-animate">スクロールで表示3</div>/* スクロール駆動アニメーション(Scroll-driven Animations) */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.scroll-animate {
animation: fadeInUp linear both;
/* スクロール位置を時間軸として使用 */
animation-timeline: view();
/* 要素がビューポートに入り始めてから30%まで再生 */
animation-range: entry 0% entry 30%;
}animation-timeline: view() を指定した要素は、スクロール位置に合わせてアニメーションが進みます。animation-range: entry 0% entry 30% は「要素がビューポートに入り始めた瞬間(entry 0%)から、30%まで見えた状態(entry 30%)の間でアニメーションを完了させる」という意味です。
animation の linear はスクロール速度に比例してアニメーションが進む指定で、both はアニメーション前後どちらも指定状態を維持する指定です。
@supports で対応状況を検出し、非対応ブラウザにはIntersection Observer ベースの実装を適用します。
/* フォールバック:未対応ブラウザはJSで対応 */
@supports not (animation-timeline: view()) {
.scroll-animate {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.7s ease-out, transform 0.7s ease-out;
}
.scroll-animate.is-visible {
opacity: 1;
transform: translateY(0);
}
}フォールバック用のJavaScriptはSection 3のIntersection Observerのコードをそのまま流用できます。@supports not (animation-timeline: view()) ブロック内のCSSは対応ブラウザには適用されないため、両方書いてもスタイルが衝突することはありません。
| ブラウザ | Scroll-driven Animations対応状況 | 備考 |
|---|---|---|
| Chrome 115+ | 対応 | 最も早く実装 |
| Edge 115+ | 対応 | ChromiumベースのためChromeと同じ |
| Firefox 110+ | 対応(一部フラグ解除が必要) | フラグ不要は124+から |
| Safari 17.2+ | 部分対応 | 全機能は未対応。本番投入は要検討 |
| iOS Safari | 部分対応 | PCと同様の制約あり |
将来的にはSafariの対応が進み、JavaScriptなしのスクロールアニメーションが標準になると見られています。今のうちに書き方を把握しておき、本番投入のタイミングはブラウザシェアと相談しながら判断しましょう。
画面上の動きに敏感なユーザーにとって、アニメーションは頭痛・めまい・不快感を引き起こすことがあります。前庭機能障害(平衡感覚に関わる障害)を持つ方、てんかんの方、または単純に動きが苦手な方に対して、アニメーションを無効化したり、控えめにしたりする配慮が重要です。
OS やブラウザには「動きを減らすモード」の設定があります。macOS なら「アクセシビリティ」→「視覚的エフェクトを減らす」、Windows なら「システム設定」→「アニメーションを表示する」のオフがそれにあたります。CSSではこの設定を prefers-reduced-motion メディアクエリで検出できます。
/* 基本のフェードインアニメーション */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.8s ease-out forwards;
}
/* 動きを減らす設定を優先するメディアクエリ */
@media (prefers-reduced-motion: reduce) {
.fade-in {
/* アニメーションを無効化 */
animation: none;
/* 最終状態(表示済み)を即時適用 */
opacity: 1;
transform: none;
}
}prefers-reduced-motion: reduce が有効な場合、animation: none でアニメーションを無効化し、opacity: 1 と transform: none で最終状態を即時適用します。「要素が見えない状態のまま止まってしまう」ことを防ぐのが重要です。
/* Intersection Observer 使用パターンでの配慮 */
.scroll-fade {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.7s ease-out, transform 0.7s ease-out;
}
.scroll-fade.is-visible {
opacity: 1;
transform: translateY(0);
}
/* 動きが苦手なユーザー:即時表示 */
@media (prefers-reduced-motion: reduce) {
.scroll-fade,
.scroll-fade.is-visible {
opacity: 1;
transform: none;
transition: none;
}
}.scroll-fade と .scroll-fade.is-visible の両方を対象にすることで、クラスが付与されているかどうかに関係なく、常に opacity: 1 が維持されます。JavaScriptのIntersection Observer処理自体は残しておいて構いません(クラスが付与されても表示状態は変わらないので無害です)。
「デフォルトはアニメーションなし、動きを許可する設定のときだけ有効にする」という書き方もあります。アクセシビリティファーストの考え方です。
/* さらに安全な書き方:最初からアニメーション無効を宣言してから上書き */
.fade-in {
/* デフォルト:アニメーションなし */
animation: none;
opacity: 1;
}
/* 動きを許可する環境でのみアニメーション適用 */
@media (prefers-reduced-motion: no-preference) {
.fade-in {
opacity: 0;
animation: fadeIn 0.8s ease-out forwards;
}
}この書き方は「ユーザーが明示的に動きを許可していない限りアニメーションしない」という最も安全なアプローチです。大規模サービスや公共機関のサイトなど、アクセシビリティ要件が厳しい場合に向いています。
| 対応レベル | 実装方法 | 向いているプロジェクト |
|---|---|---|
| 最低限 | prefers-reduced-motion: reduce でアニメーション無効化 | 個人サイト・ポートフォリオ |
| 推奨 | 両方のケースを明示的にCSSで書く | 企業サイト・Webサービス |
| 最高水準 | prefers-reduced-motion: no-preference でのみ有効化 | 公共機関・アクセシビリティ重視サービス |
CSSフェードインを実装したけれど「アニメーションしない」「一瞬で表示される」「要素が消えたまま」といったトラブルはよく起きます。原因のほとんどはパターンが決まっているので、順番に確認していきましょう。
@keyframes でフェードインを実装した場合、animation: fadeIn 1s ease だけでは「アニメーション終了後に元の状態(opacity: 0)に戻る」動作になります。必ず forwards を付けて、終了状態を維持させましょう。
animation: fadeIn 1s ease;animation: fadeIn 1s ease forwards;display: none の要素に transition を適用しても動きません。display プロパティ自体が transition の対象外だからです。代わりに opacity と visibility を組み合わせて使いましょう。
/* display: noneとtransitionは併用できない! */
/* NG例 */
.wrong {
display: none;
transition: opacity 0.5s; /* 効かない */
}
.wrong.is-visible {
display: block; /* display変更はtransitionできない */
opacity: 1;
}
/* OK例:visibilityを使う */
.correct {
opacity: 0;
visibility: hidden; /* クリックやキーボード操作を無効化 */
transition: opacity 0.5s, visibility 0.5s;
}
.correct.is-visible {
opacity: 1;
visibility: visible;
}transition を使う場合、.fade-target に opacity: 0 を明示していないと「最初から見えている状態」でアニメーションが始まります。クラス付与前の初期状態を必ずCSSで定義しておきましょう。
ブラウザの開発者ツールで対象要素を選択し、「Elements」タブで is-visible クラスが付いているか確認します。付いていない場合はJavaScript側のセレクタやタイミングの問題です。
DOMContentLoaded より前にスクリプトが実行されていないかobserve() が正しく呼ばれているか他のCSSが opacity: 1 !important で上書きしていたり、逆に opacity: 0 が別のルールで固定されていたりすることがあります。開発者ツールの「Computed」タブで最終的に適用されている値を確認しましょう。
/* z-indexが効かない原因あるある:positionを忘れている */
.overlay {
position: relative; /* これがないとz-indexが効かない */
z-index: 10;
opacity: 0;
transition: opacity 0.5s;
}z-index の問題で要素が他の要素の裏側に隠れているケースもあります。z-index を使う場合は必ず position: relative / absolute / fixed のいずれかを指定することを覚えておきましょう。
アニメーションが「カクつく」「ちらつく」場合は、GPUアクセラレーションが効いていない可能性があります。will-change を使ってブラウザに事前通知することで、パフォーマンスが改善されることがあります。
/* パフォーマンス改善:will-changeで事前にGPU描画を有効化 */
.fade-in {
will-change: opacity, transform;
opacity: 0;
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}
/* アニメーション完了後はwill-changeを外す */
.fade-in.is-visible {
opacity: 1;
transform: none;
will-change: auto;
}will-change を付けすぎるとメモリ消費が増えるため、アニメーションする要素だけに絞ってください。使い終わったら will-change: auto に戻すことをおすすめします。
| 症状 | よくある原因 | 対処法 |
|---|---|---|
| アニメーションしない | display: none と transition を併用 | visibility + opacity に変更 |
| アニメーション後に消える | forwards の指定忘れ | animation に forwards を追加 |
| 最初から見えている | 初期値のopacity: 0が未設定 | CSSに opacity: 0 を明示 |
| クラスが付かない | JS のセレクタミス・タイミング問題 | 開発者ツールでDOMを確認 |
| カクカクする | GPUアクセラレーション未適用 | will-change: opacity, transform を追加 |
| 一部のブラウザだけ効かない | animation-timeline 未対応 | @supports でフォールバックを実装 |
CSSフェードインとは、要素の透明度(opacity)を0(完全に透明)から1(完全に不透明)へCSSのアニメーション機能で変化させることで、要素がふわっと現れる視覚効果のことです。@keyframesやtransitionプロパティを使って実装します。
基本的にはIntersection Observer APIという少量のJavaScriptが必要です。ただし、Chrome・Edge・Firefoxの最新版であればCSSのanimation-timeline: view()を使うことでJavaScriptなしで実装できます。Safariの対応が完全でないため、本番環境では@supportsでフォールバックを用意することをおすすめします。
初期状態にopacity: 0とtransform: translateY(40px)を指定し、表示状態ではopacity: 1とtransform: translateY(0)にすることで実現します。transitionプロパティで変化の時間とイージングを設定し、Intersection Observerでスクロール時にクラスを付与するのが定番の実装パターンです。
よくある原因はいくつかあります。①@keyframesアニメーションにforwardsを忘れた(終了後に元に戻る)、②display: noneとtransitionを組み合わせている(displayはtransition不可)、③初期値のopacity: 0が未指定、④JavaScriptのセレクタミスでクラスが付与されていない、の4点を順番に確認してください。
CSSのprefers-reduced-motionメディアクエリを使います。@media (prefers-reduced-motion: reduce)ブロック内でanimation: noneを指定し、opacity: 1とtransform: noneを設定することで、動きを減らす設定を有効にしているユーザーには即時表示されるようになります。アクセシビリティ対応として必須の実装です。
CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?
未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!
WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。
公式サイト より
今すぐ
無料カウンセリング
を予約!