2026年5月20日

2026年5月20日

WordPressでスティッキーヘッダー(追従ヘッダー)を実装する方法

WordPressでスクロールしてもヘッダーが上部に残る「スティッキーヘッダー」を作るなら、まずCSSの position: sticky を使うのが最も手軽で、表示崩れやレイアウトのずれ(CLS)も起きにくい方法です。この記事では、position: sticky の基本、効かないときの原因、ログイン時に管理バーと重なる問題の対処、JavaScriptが必要になる場面とCLSを出さない書き方、ブロックテーマでの設定までを順に解説します。

position: stickyで固定する

スティッキーヘッダーの基本は、ヘッダー要素に次の3行を指定するだけです。

/* 子テーマの style.css または「追加CSS」 */
.site-header {
	position: sticky;
	top: 0;
	z-index: 100;
	background: #fff; /* 背景を指定しないと下の本文が透けて見える */
}

position: sticky は、要素が通常の位置にある間は普段どおり配置され、スクロールで top に指定した位置まで来るとそこに留まる指定です。top の指定がないと固定されません。z-index は、本文中の画像やボタンがヘッダーの上に重なって見えるのを防ぐためのものです。

.site-header の部分は使っているテーマに合わせて変えてください。ブラウザの開発者ツールでヘッダー全体を囲む要素を選び、その class を確認します。クラシックテーマでは header.php の中の <header> 要素にクラスが付いていることが多く、親テーマを直接編集すると更新で消えるため、CSSは子テーマか管理画面の「追加CSS」に書きます。

この方法の利点は、ヘッダーが文書の流れから外れない点です。ヘッダー分の高さは最初から確保されているので、固定が始まった瞬間に本文が上へずれることがありません。position: fixed を使う方法と比べて、本文の上に余白を足す調整も不要です。

固定されないときに確かめること

position: sticky を指定しても動かない場合、原因の多くは次の3つです。

親要素がヘッダーと同じ高さしかない

stickyは、その要素を含む親要素の範囲の中でしか固定されません。テーマによってはヘッダーが <div class="header-wrapper"> などで囲まれていて、その親要素の高さがヘッダーと同じになっています。この場合、親要素が画面外に出た時点でヘッダーも一緒に流れていきます。

対処は、sticky の指定を外側の要素に付け直すことです。開発者ツールで親をたどり、<body> 直下やページ全体を囲む要素の子になっている要素に position: sticky を指定します。

祖先要素にoverflowが指定されている

ヘッダーの祖先要素のどこかに overflow: hidden、overflow: auto、overflow: scroll が指定されていると、その要素がスクロールの基準になり、ページのスクロールでは固定されなくなります。横はみ出し対策として body や全体のラッパーに overflow-x: hidden を付けているテーマで起きやすい問題です。

横方向のはみ出しを防ぐ目的なら、overflow-x: clip に置き換えると、stickyを壊さずにはみ出しを隠せます。clip はスクロールの基準を作らないためです。

指定が上書きされている

テーマのCSSが同じ要素に position: relative などを指定していると、後から読み込まれた側や詳細度の高い側が勝ちます。開発者ツールの「スタイル」で、自分の指定に取り消し線が付いていないか確認してください。

ログイン時に管理バーと重なる問題

WordPressにログインした状態でサイトを見ると、画面上部に管理バーが固定表示されます。top: 0 のままだと、スティッキーヘッダーが管理バーの裏に潜り込んでしまいます。

ログイン中の <body> には admin-bar クラスが付くので、それを使ってずらします。近年のWordPressでは、管理バーの高さが --wp-admin--admin-bar--height というCSS変数で出力されているため、これを参照すると画面幅ごとの高さの違いにも追従できます。

body.admin-bar .site-header {
	/* 変数が無い環境では32pxを使う */
	top: var(--wp-admin--admin-bar--height, 32px);
}

/* 幅600px以下では管理バーが固定表示されずスクロールで流れるため、0に戻す */
@media screen and (max-width: 600px) {
	body.admin-bar .site-header {
		top: 0;
	}
}

管理バーの高さは、パソコン幅では32px、幅782px以下では46pxです。さらに幅600px以下では管理バー自体が固定されなくなるため、上の例のように top: 0 に戻しておかないと、スクロール後にヘッダーの上に不自然な隙間ができます。

この問題はログインしている人にしか見えないため、気づかないまま公開されがちです。動作確認はログイン中と、ログアウトした状態(またはシークレットウィンドウ)の両方で行ってください。

ページ内リンクの見出しがヘッダーに隠れる

目次などのページ内リンクで見出しへ移動すると、見出しがスティッキーヘッダーの下に隠れることがあります。これは scroll-padding-top で解決できます。

html {
	/* ヘッダーの高さ+少しの余白(80pxは実際のヘッダーの高さに合わせる)。
	   管理バーの変数が無いとき(ログアウト時)は0pxとして計算する */
	scroll-padding-top: calc(80px + var(--wp-admin--admin-bar--height, 0px));
}

scroll-padding-top はスクロールする要素(通常のページでは html)に指定しないと効きません。body に書いても反映されない点に注意してください。ヘッダーの高さが画面幅で変わる場合は、メディアクエリで値を切り替えます。

JavaScriptが必要な場面とCLSを避ける書き方

「スクロールしたら影を付けたい」「スクロールしたらヘッダーを小さくしたい」といった演出には、JavaScriptでクラスを付け外しする必要があります。ここで注意したいのが、CLS(Cumulative Layout Shift、予期しないレイアウトのずれ)です。

CLSが悪化しやすいのは次のような実装です。

  • スクロール量を見て、ヘッダーを position: static から position: fixed に切り替える。ヘッダーが流れから外れた瞬間に、本文がヘッダーの高さ分だけ上へ飛ぶ
  • スクロールに合わせてヘッダーの height や padding を小さくする。ヘッダーの高さが変わるたびに、下の本文が動く

これを避けるには、固定自体は position: sticky に任せ、JavaScriptでは見た目だけを変えるのが基本です。影、背景色、ロゴの transform: scale() など、周りの要素の位置に影響しないプロパティを変えれば、レイアウトのずれは起きません。

スクロールの検知には、scroll イベントで毎回位置を計算するより、IntersectionObserver で「ページ先頭の目印が見えなくなったか」を監視するほうが負荷が軽くなります。

.site-header {
	transition: box-shadow 0.2s ease;
}
.site-header.is-scrolled {
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
@media (prefers-reduced-motion: reduce) {
	.site-header {
		transition: none;
	}
}
// assets/js/sticky-header.js
document.addEventListener('DOMContentLoaded', function () {
	var header = document.querySelector('.site-header');
	if (!header || !('IntersectionObserver' in window)) {
		return;
	}

	// ページ先頭に高さ1pxの目印を置き、それが画面外に出たらクラスを付ける
	var sentinel = document.createElement('div');
	sentinel.setAttribute('aria-hidden', 'true');
	sentinel.style.cssText = 'position:absolute;top:0;left:0;width:1px;height:1px;';
	document.body.prepend(sentinel);

	var observer = new IntersectionObserver(function (entries) {
		header.classList.toggle('is-scrolled', !entries[0].isIntersecting);
	});
	observer.observe(sentinel);
});

目印は position: absolute で配置しているので、ページのレイアウトには影響しません。このスクリプトは子テーマの functions.php から読み込みます。

add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_script(
		'mytheme-sticky-header',
		get_stylesheet_directory_uri() . '/assets/js/sticky-header.js',
		array(),
		'1.0.0',
		array(
			'strategy'  => 'defer',
			'in_footer' => true,
		)
	);
} );

第5引数の配列で strategy を指定する書き方はWordPress 6.3以降のものです。それより古い環境では第5引数に true を渡してフッターで読み込んでください。

スマートフォンでの注意点

スマートフォンでは画面の高さが限られるため、ヘッダーが高いと本文の表示領域が大きく削られます。追従させるのはロゴとメニューボタンだけの細い帯にする、狭い画面では追従させない、といった判断も必要です。

/* 例: 横向きのスマートフォンなど、高さの低い画面では追従させない */
@media (max-height: 500px) {
	.site-header {
		position: static;
	}
}

メニューを開いたときにヘッダー内で展開する作りの場合、展開したメニューが画面の高さを超えると下の項目に届かなくなります。メニュー部分に max-height と overflow-y: auto を指定し、メニュー内でスクロールできるようにしておくと安心です。

ブロックテーマでの設定

Twenty Twenty-Fourなどのブロックテーマでは、サイトエディターでヘッダーのテンプレートパーツを開き、ヘッダー全体を囲むグループブロックを選ぶと、設定パネルの「位置」で固定(sticky)を選べます。この設定はWordPress 6.2で追加されたもので、CSSを書かずに position: sticky 相当を指定できます。ただし、この設定はテンプレートの最上位に近いブロック向けのもので、入れ子の深いブロックでは選択肢が表示されないことがあります。その場合はヘッダー全体を囲む外側のブロックを選び直してください。また背景色を設定しないと本文が透けて見える点はCSSで実装する場合と同じです。画面の表記はバージョンによって変わることがあります(2026年10月現在)。

まとめ

  • スティッキーヘッダーはCSSの position: sticky; top: 0; が基本。背景色と z-index も指定する
  • stickyは流れから外れないため、固定が始まっても本文がずれずCLSが起きにくい
  • 効かないときは、親要素の高さ、祖先の overflow、指定の上書きを確認する。はみ出し対策は overflow-x: clip が使える
  • ログイン時は body.admin-bar と --wp-admin--admin-bar--height で管理バーの分ずらし、幅600px以下では top: 0 に戻す
  • ページ内リンクの見出し隠れは scroll-padding-top で防ぐ
  • JavaScriptでは高さや position を変えず、影や transform など見た目だけを変える。検知は IntersectionObserver が軽い
  • ブロックテーマはグループブロックの「位置」設定でも固定できる

お気軽にご相談ください

お見積りへ お問い合わせへ
▲