2026年9月9日
2026年9月9日
WordPressでCSSアニメーションとIntersection Observerを使ったスクロール演出を実装する方法
スクロールして要素が画面に入ったときにふわっと表示させる演出は、プラグインを使わなくても、CSSのトランジションとブラウザ標準のIntersection Observer APIで実装できます。仕組みは「画面に入った要素にクラスを付け、そのクラスでCSSのアニメーションを動かす」だけで、スクロールのたびに位置を計算する方式より軽く動きます。この記事では、子テーマでのファイルの読み込み方、JavaScriptとCSSのコード、ブロックエディターでの使い方、動きを減らす設定(prefers-reduced-motion)への対応、レイアウトのずれ(CLS)や表示速度を悪化させないための注意点までを説明します。
この方式を選ぶ理由
以前は、scroll イベントで毎回要素の位置を調べる方法がよく使われていました。しかしスクロール中に何度も位置計算が走るため、ページが重いとカクつく原因になります。Intersection Observer は「要素が画面(または指定した範囲)に入った・出た」をブラウザが判定して通知してくれる仕組みで、自分で位置を計算する必要がありません。現在の主要ブラウザで使えます(2026年10月現在)。
もう一つの考え方として、CSSだけでスクロール位置に連動させる「スクロール駆動アニメーション」(animation-timeline: view() など)もあります。JavaScriptが不要なのが利点ですが、対応状況がブラウザによって異なるため、使う場合は @supports で対応ブラウザに限定し、非対応のブラウザでは普通に表示されるようにしておきます。この記事では、対応範囲が広い Intersection Observer の方式を中心に説明します。
設計の方針:JavaScriptが動かなくても内容は見える
スクロール演出で一番避けたいのは、「スクリプトが読み込まれなかった結果、要素が透明のまま見えない」という状態です。そこで次の順番で組み立てます。
- 演出したい要素に
revealクラスを付けておく(この時点では普通に表示される) - JavaScriptが動いたときだけ、
<html>にreveal-readyクラスを付ける reveal-readyがあるときだけ、revealの要素を透明にしておく- 画面に入った要素に
is-visibleを付けて表示させる
こうしておくと、スクリプトのエラーや読み込みの失敗、JavaScriptを無効にしている環境でも、内容はそのまま読めます。
ファイルを用意して読み込む
子テーマの中に assets フォルダを作り、reveal.css と reveal.js を置きます。読み込みは子テーマの functions.php で行います。テーマのヘッダーに <script> タグを直接書くのではなく、wp_enqueue_style() と wp_enqueue_script() を使うことで、依存関係やキャッシュ対策、ほかのプラグインとの読み込み順をWordPressが管理できるようになります。
add_action( 'wp_enqueue_scripts', function () {
$dir = get_stylesheet_directory();
$uri = get_stylesheet_directory_uri();
wp_enqueue_style(
'mytheme-reveal',
$uri . '/assets/reveal.css',
array(),
filemtime( $dir . '/assets/reveal.css' )
);
wp_enqueue_script(
'mytheme-reveal',
$uri . '/assets/reveal.js',
array(),
filemtime( $dir . '/assets/reveal.js' ),
array(
'strategy' => 'defer',
'in_footer' => true,
)
);
} );
バージョン番号に filemtime() を使うと、ファイルを更新したときにURLの末尾が変わるため、ブラウザに古いファイルが残る問題を避けられます。wp_enqueue_script() の第5引数に配列で strategy を渡す書き方は WordPress 6.3 以降で使えます。それより古い環境では true(フッターで読み込む)を渡してください。
JavaScript:画面に入った要素にクラスを付ける
assets/reveal.js の内容です。
( function () {
var items = document.querySelectorAll( '.reveal' );
if ( ! items.length ) {
return;
}
var reduce = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
if ( reduce || ! ( 'IntersectionObserver' in window ) ) {
return; // 演出なし。要素は最初から表示されたまま
}
// 読み込み時点ですでに画面内にある要素は、最初から表示済みにする
items.forEach( function ( el ) {
if ( el.getBoundingClientRect().top < window.innerHeight ) {
el.classList.add( 'is-visible' );
}
} );
document.documentElement.classList.add( 'reveal-ready' );
var observer = new IntersectionObserver( function ( entries, obs ) {
entries.forEach( function ( entry ) {
if ( entry.isIntersecting ) {
entry.target.classList.add( 'is-visible' );
obs.unobserve( entry.target );
}
} );
}, {
rootMargin: '0px 0px -10% 0px',
threshold: 0.1
} );
items.forEach( function ( el ) {
if ( ! el.classList.contains( 'is-visible' ) ) {
observer.observe( el );
}
} );
} )();
コードの考え方を補足します。
- 動きを減らす設定の確認:OSで「視差効果を減らす」「アニメーションを減らす」などを有効にしている利用者には、演出をせず最初から表示します。動きで気分が悪くなる人への配慮です。
- すでに画面内にある要素:スクリプトは
deferでHTMLの解析後に動くため、そのまま透明にすると、最初に見えていた要素が一度消えてからフェードインする「ちらつき」が起きます。読み込み時点で画面内にある要素は先にis-visibleを付けておき、これを防いでいます。 - 一度表示したら監視をやめる:
unobserve()で監視を外すと、上下にスクロールするたびに出たり消えたりせず、処理も軽くなります。 - **
rootMarginとthreshold**:-10%は画面の下端より少し上に入ったときに反応させる設定、0.1は要素の10%が見えたら反応する設定です。好みに合わせて調整します。
CSS:透明から表示へトランジションする
assets/reveal.css の内容です。
.reveal-ready .reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal-ready .reveal.is-visible {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
.reveal-ready .reveal {
opacity: 1;
transform: none;
transition: none;
}
}
アニメーションさせるのは opacity と transform だけにしています。この2つは要素の配置を変えないため、周りの要素を押しのけて動かすことがなく、ブラウザの処理も軽く済みます。margin や top、height を動かすと、周りの要素の位置が変わり、レイアウトのずれ(CLS)の原因になります。
JavaScript側で判定していても、CSS側にも prefers-reduced-motion の指定を入れておくと、表示中に利用者が設定を変えた場合なども含めて動きを抑えられます。
複数のカードを順番に表示したい場合は、transition-delay を少しずつずらします。
.reveal-ready .reveal-stagger > .reveal:nth-child(2) { transition-delay: 0.1s; }
.reveal-ready .reveal-stagger > .reveal:nth-child(3) { transition-delay: 0.2s; }
ブロックエディターで使う
投稿や固定ページで演出を付けたいブロックを選び、設定パネルの「高度な設定」にある「追加CSSクラス」の欄に reveal と入力します(パネルの表記はバージョンにより異なります)。カラムを順番に表示したい場合は、外側のカラムブロックに reveal-stagger、中の各カラムに reveal を付けます。
reveal-ready はフロント側のスクリプトでしか付かないため、ブロックエディターの中では要素が透明にならず、普段どおり編集できます。
表示速度とアクセシビリティの注意
- ファーストビューには使わない:ページを開いて最初に見える見出しやメイン画像を演出の対象にすると、表示されるまでの時間が延び、LCP(最大コンテンツの描画)の評価が悪くなるおそれがあります。演出はスクロールした先の要素に限るのが無難です。
- 使いすぎない:すべての段落が動くページは、読む側にとって落ち着きません。区切りとなる見出しや画像、カードなど要所に絞ります。
- 内容を隠す用途に使わない:重要な情報が演出の完了まで読めない設計は避けます。前述の方針どおり、スクリプトが動かなくても読める状態を保ちます。
- キャッシュと最適化プラグイン:JavaScriptの結合や遅延読み込みを行う最適化プラグインを使っていると、読み込み順が変わって動かないことがあります。動かないときは、ブラウザの開発者ツールのコンソールでエラーを確認し、最適化の対象から外して試します。
動かないときの確認ポイント
- ファイルが読み込まれているか:開発者ツールのネットワークタブで
reveal.jsとreveal.cssが200で取得できているかを見ます。404ならパスやファイル名を確認します。 - クラス名が付いているか:要素の検証で、
<html>にreveal-ready、対象要素にrevealが付いているかを確認します。 - OSの設定:動きを減らす設定が有効だと、意図どおり演出が止まります。確認の際はこの設定を見落とさないようにします。
- 作業前の準備:
functions.phpを編集する前にバックアップを取り、ステージング環境で確認してから本番に反映してください。
まとめ
- Intersection Observer で画面に入った要素にクラスを付け、CSSのトランジションで表示させると、軽くて扱いやすいスクロール演出になります。
- ファイルは子テーマに置き、
wp_enqueue_style()とwp_enqueue_script()で読み込みます。 - JavaScriptが動かない環境でも内容が見えるよう、透明にするのは
reveal-readyがあるときだけにします。 prefers-reduced-motionを尊重し、動かすのはopacityとtransformに限ってレイアウトのずれを防ぎます。- ファーストビューの要素には演出を付けず、表示速度への影響を抑えます。