WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

CSSフェードインの実装方法|スクロールで要素をふわっと表示する(JS最小)

CSSフェードインの実装方法のアイキャッチ

「ボタンを押したわけでもないのに、スクロールするだけで要素がふわっと現れる」——あのアニメーション、気になったことはありませんか?あれが CSSフェードイン です。実装の仕方によって「ページを開いた瞬間に表示される」パターンと「スクロールして画面に入ったら現れる」パターンの2種類があり、それぞれ用途が異なります。

CSSフェードインは opacity(透明度)を 0 から 1 に変化させることで実現します。たった数行のコードで視覚的な滑らかさが生まれ、ページ全体の印象がプロっぽくなります。最近ではJavaScriptをほぼ書かずに実現できる方法も増えており、初心者でも取り組みやすい技術になってきました。

この記事では基本的な @keyframes アニメーションから、スクロール連動、方向付きアニメーション、時間差で複数要素を表示する方法、さらにCSSだけで実現する最新手法まで順を追って解説します。コードをコピーしてそのまま使えるように設計しているので、実際のプロジェクトにすぐ活かせます。

まずはこの記事で学べることをまとめておきます。

  • ページ読込時にふわっと表示@keyframestransition を使って、ページを開いた瞬間に要素がフェードインする方法
  • スクロールで画面に入ったら表示:Intersection Observer API を使って、スクロールで見えてきたタイミングで要素を表示する方法
  • 方向・時間差アニメーション:下から・左から・右からのフェードインや、複数要素を順番に表示するテクニック
  • アクセシビリティ対応:動きが苦手なユーザーへの配慮と、フェードインが効かないときのトラブルシューティング

ページ読込時のフェードイン(@keyframesで実装)

ページを開いた瞬間に要素がふわっと表示されるパターンです。最もシンプルな方法は @keyframes を使ってアニメーションを定義し、対象要素に animation プロパティで適用することです。JavaScriptは一切不要で、CSSだけで完結します。

opacity は要素の透明度を制御するプロパティです。0 が完全に透明、1 が完全に不透明になります。@keyframesfrom(開始状態)に opacity: 0to(終了状態)に opacity: 1 を指定することで、透明から不透明への変化が生まれます。

基本的な書き方

/* フェードインアニメーションの定義 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 要素に適用 */
.fade-in {
  animation: fadeIn 1s ease forwards;
}

animation プロパティには「アニメーション名・再生時間・イージング・終了後の状態」をまとめて指定しています。forwards は「アニメーション終了後も最後の状態(opacity: 1)を維持する」という意味です。これを忘れると、アニメーション完了後に要素が消えてしまうので注意しましょう。

遅延(delay)を加えてタイミングをずらす

/* 遅延ありフェードイン */
.fade-in-delay {
  opacity: 0; /* 初期状態は透明 */
  animation: fadeIn 1s ease 0.5s forwards;
  /* アニメーション名 / 時間 / イージング / 遅延 / 終了後の状態 */
}

animation の4番目の値が遅延時間です。ここでは 0.5s(0.5秒後)に再生が始まります。遅延中は初期状態(opacity: 0)のままになるので、要素に opacity: 0 を初期値として指定しておくことが大切です。

HTMLとCSSをセットにした完全サンプル

<!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 程度を基準に微調整しましょう。

transitionを使ったフェードイン(クラス付与で表示切り替え)

@keyframes 以外のアプローチとして、transition プロパティを使う方法があります。こちらは「状態変化のとき自動でアニメーションを補間する」仕組みです。要素の初期状態を opacity: 0 にしておき、JavaScriptでクラスを付与するだけで滑らかにフェードインします。

@keyframes との違いは「トリガーを自分でコントロールしやすい」点にあります。ボタンクリック時・メニューが開いたとき・データ取得完了後など、任意のタイミングで発火できます。スクロール連動には別途Intersection Observerが必要ですが(Section 4で解説)、クリックや状態変化のトリガーには transition が最適です。

基本構成:CSS+最小限のJS

/* 初期状態:透明 */
.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') を呼ぶだけ。これだけで opacity0 → 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マウスオーバー不要ボタン・リンクの視覚フィードバック

スクロールで要素を表示する方法(Intersection Observer)

「スクロールして要素が画面に入ってきたタイミングでフェードイン」という表現は、現代のウェブサイトで最も多く使われるパターンのひとつです。以前は scroll イベントを使ってスクロール量を計算する方法が主流でしたが、パフォーマンスの問題から現在は Intersection Observer API を使うのが王道です。

Intersection Observer は「要素がビューポート(画面の見える領域)に入ったか出たか」を効率よく検知するブラウザ標準のAPIです。scroll イベントと違い、毎フレーム実行されるわけではなくブラウザが最適なタイミングで呼び出すため、重い処理になりません。2024年時点で主要ブラウザのサポート率はほぼ100%です。

完全サンプル(HTML + CSS + JS)

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.isIntersectingtrue になったとき、その要素が画面内に入ったことを意味します。

  • 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 で非表示にして visibilitydisplay は変更しない設計にしましょう。

方向付きフェードイン(下から・左から・右から)

「ただ透明度が変わるだけ」より「下からふわっと浮き上がる」「右からスライドしてくる」ような動きのほうが、視覚的にメリハリがあります。これを実現するのが transform プロパティとの組み合わせです。opacity と同時に translateX / translateY(位置のずれ)もアニメーションさせることで、方向のある動きを作れます。

ポイントは「初期値に位置のずれを持たせ、表示時に translate(0, 0) に戻す」ことです。フェードアウト先を定義するのではなく、フェードイン元を定義するイメージです。

方向別クラスの早見表

クラス名初期値(transform)動きの方向用途例
.fade-uptranslateY(40px)下から上へ浮き上がる本文テキスト・カード
.fade-downtranslateY(-40px)上から下へ落ちてくるナビゲーション・ヘッダー
.fade-lefttranslateX(40px)右から左へスライド右サイドバー・画像
.fade-righttranslateX(-40px)左から右へスライド左サイドバー・見出し

CSS実装

/* 共通トランジション設定 */
.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);
}

HTML

<div class="fade-up">下から上へフェードイン</div>
<div class="fade-left">右から左へフェードイン</div>
<div class="fade-right">左から右へフェードイン</div>
<div class="fade-down">上から下へフェードイン</div>

JavaScript(Intersection Observer)

// 全方向フェードイン要素を一括監視
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アニメーション)

「リストの各項目が順番にふわっと現れる」「カードが一枚ずつタイミングをずらして表示される」——これを stagger(スタッガー)アニメーション と呼びます。全要素が一斉に表示されるより、時間差をつけたほうがリズムが生まれ、情報が読み取りやすくなります。

CSSで時間差を作る方法は主に2つあります。① nth-child で各要素に異なる transition-delay を指定する方法と、② CSSカスタムプロパティ(変数)と calc() を組み合わせてHTML側で遅延の係数を渡す方法です。

方法①:nth-child で時間差を指定

<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カスタムプロパティで遅延を動的計算

要素数が動的に変わる場合や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));

--i0, 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追加が必要

最新CSSだけで実装する方法(animation-timeline: view())

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%)の間でアニメーションを完了させる」という意味です。

animationlinear はスクロール速度に比例してアニメーションが進む指定で、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なしのスクロールアニメーションが標準になると見られています。今のうちに書き方を把握しておき、本番投入のタイミングはブラウザシェアと相談しながら判断しましょう。

アクセシビリティ配慮(prefers-reduced-motion)

画面上の動きに敏感なユーザーにとって、アニメーションは頭痛・めまい・不快感を引き起こすことがあります。前庭機能障害(平衡感覚に関わる障害)を持つ方、てんかんの方、または単純に動きが苦手な方に対して、アニメーションを無効化したり、控えめにしたりする配慮が重要です。

OS やブラウザには「動きを減らすモード」の設定があります。macOS なら「アクセシビリティ」→「視覚的エフェクトを減らす」、Windows なら「システム設定」→「アニメーションを表示する」のオフがそれにあたります。CSSではこの設定を prefers-reduced-motion メディアクエリで検出できます。

@keyframesアニメーションへの対応

/* 基本のフェードインアニメーション */
@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: 1transform: none で最終状態を即時適用します。「要素が見えない状態のまま止まってしまう」ことを防ぐのが重要です。

Intersection Observer パターンへの対応

/* 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フェードインを実装したけれど「アニメーションしない」「一瞬で表示される」「要素が消えたまま」といったトラブルはよく起きます。原因のほとんどはパターンが決まっているので、順番に確認していきましょう。

チェック1:animation に forwards を忘れていないか

@keyframes でフェードインを実装した場合、animation: fadeIn 1s ease だけでは「アニメーション終了後に元の状態(opacity: 0)に戻る」動作になります。必ず forwards を付けて、終了状態を維持させましょう。

  • NG: animation: fadeIn 1s ease;
  • OK: animation: fadeIn 1s ease forwards;

チェック2:display: none とtransitionを組み合わせていないか

display: none の要素に transition を適用しても動きません。display プロパティ自体が transition の対象外だからです。代わりに opacityvisibility を組み合わせて使いましょう。

/* 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;
}

チェック3:初期値のopacityが設定されているか

transition を使う場合、.fade-targetopacity: 0 を明示していないと「最初から見えている状態」でアニメーションが始まります。クラス付与前の初期状態を必ずCSSで定義しておきましょう。

チェック4:JavaScriptでクラスが正しく付与されているか

ブラウザの開発者ツールで対象要素を選択し、「Elements」タブで is-visible クラスが付いているか確認します。付いていない場合はJavaScript側のセレクタやタイミングの問題です。

  • セレクタのスペルミスがないか確認(クラス名のタイポ)
  • DOMContentLoaded より前にスクリプトが実行されていないか
  • Intersection Observerの observe() が正しく呼ばれているか

チェック5:CSSの詳細度(specificity)で上書きされていないか

他のCSSが opacity: 1 !important で上書きしていたり、逆に opacity: 0 が別のルールで固定されていたりすることがあります。開発者ツールの「Computed」タブで最終的に適用されている値を確認しましょう。

チェック6:z-indexやpositionが影響していないか

/* 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 のいずれかを指定することを覚えておきましょう。

チェック7:will-changeで対応できるかも

アニメーションが「カクつく」「ちらつく」場合は、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 でフォールバックを実装
目次

よくある質問(FAQ)

CSSフェードインとはどういう意味ですか?

CSSフェードインとは、要素の透明度(opacity)を0(完全に透明)から1(完全に不透明)へCSSのアニメーション機能で変化させることで、要素がふわっと現れる視覚効果のことです。@keyframesやtransitionプロパティを使って実装します。

スクロールでフェードインするにはJavaScriptが必要ですか?

基本的にはIntersection Observer APIという少量のJavaScriptが必要です。ただし、Chrome・Edge・Firefoxの最新版であればCSSのanimation-timeline: view()を使うことでJavaScriptなしで実装できます。Safariの対応が完全でないため、本番環境では@supportsでフォールバックを用意することをおすすめします。

cssフェードインで下からふわっと表示するにはどうすればよいですか?

初期状態に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・Webデザイン実装の総まとめ

CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

WithCodeを体験できる初級コース公開中!

WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次