2026年8月28日
2026年8月28日
WordPressのテーマにプログレスバー付き読み進み度インジケーターを追加する方法
記事をどこまで読んだかを画面上部の細いバーで示す読み進み度インジケーターは、子テーマに「バーのHTMLを出力するPHP」「見た目のCSS」「スクロール量を計算するJavaScript」の3つを追加すれば実装できます。この記事では、ページ全体ではなく記事本文を基準に進み具合を計算する方法、管理バーや固定ヘッダーとの重なりの解消、CSSだけで作る方法との違いまでを説明します。
仕組みと設計の考え方
読み進み度インジケーターは、スクロール位置を0〜1の割合に換算し、その割合に応じてバーの横幅を伸ばす部品です。設計で決めておきたいのは次の3点です。
- 何を基準に計算するか:ページ全体で計算すると、本文を読み終えた後もコメント欄やフッターの分だけバーが伸び続けます。投稿本文の要素を基準にすると、「本文を読み終えた時点でバーが右端に届く」自然な動きになります。
- どのページで表示するか:一覧ページや固定ページでは意味が薄いため、投稿の詳細ページに限定するのが一般的です。
- 読み上げでどう扱うか:見た目の補助であり、スクロールのたびに数値を読み上げられると邪魔になるため、装飾として
aria-hidden="true"を付けて読み上げ対象から外します。
作業は子テーマで行い、functions.php を編集する前にファイルのバックアップを取ってください。
ステップ1:バーのHTMLを出力する
バーはページのどこに置いても position: fixed で画面上部に固定されますが、<body> 開始直後に出力しておくとHTMLの構造が分かりやすくなります。そこで wp_body_open フックを使います。
<?php
// 子テーマの functions.php に追記
add_action( 'wp_body_open', 'mychild_reading_progress_markup' );
function mychild_reading_progress_markup() {
if ( ! is_singular( 'post' ) ) {
return;
}
echo '<div class="reading-progress" aria-hidden="true"><div class="reading-progress__bar"></div></div>';
}
wp_body_open はWordPress 5.2で追加されたフックです。ブロックテーマでは自動的に実行されますが、クラシックテーマではテーマの header.php が <body> タグの直後で wp_body_open() を呼んでいる必要があります。ページのソースにバーのHTMLが見当たらない場合は、header.php にこの呼び出しがあるか確認してください。呼び出しがない古いテーマでは、wp_footer フックで出力しても position: fixed なので見た目は同じになります。
ステップ2:CSSでバーの見た目を作る
子テーマに assets/css/reading-progress.css を作ります。
.reading-progress {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
height: 4px;
background: transparent;
pointer-events: none;
}
.reading-progress__bar {
height: 100%;
background: #2271b1;
transform: scaleX(0);
transform-origin: left center;
}
/* ログイン中は管理バーの下に表示する */
.admin-bar .reading-progress {
top: 32px;
}
@media screen and (max-width: 782px) {
.admin-bar .reading-progress {
top: 46px;
}
}
バーの伸び縮みを width ではなく transform: scaleX() で表現しているのは、width を変えるとスクロールのたびにレイアウトの再計算が発生し、動きがぎこちなくなりやすいためです。transform は再計算の負担が小さく、なめらかに動きます。pointer-events: none は、バーの下にあるヘッダーのリンクやボタンがクリックできなくなるのを防ぐ指定です。
管理バーの高さ(32px、画面幅782px以下で46px)は、ログイン中にバーが管理バーの裏に隠れないようにするための値です。ログアウトした状態では admin-bar クラスが付かないので、通常どおり画面の最上部に表示されます。
固定ヘッダーがあるテーマでは、バーをヘッダーの下に出したい場合に top をヘッダーの高さ分ずらします。反対にヘッダーの上に重ねたい場合は、ヘッダーより大きな z-index を指定します。
ステップ3:JavaScriptで進み具合を計算する
子テーマに assets/js/reading-progress.js を作ります。
(function () {
var bar = document.querySelector('.reading-progress__bar');
// クラシックテーマの多くは .entry-content、ブロックテーマは .wp-block-post-content
var target = document.querySelector('.entry-content, .wp-block-post-content');
if (!bar || !target) {
return;
}
var ticking = false;
function update() {
var rect = target.getBoundingClientRect();
var start = rect.top + window.scrollY;
var end = start + target.offsetHeight - window.innerHeight;
var distance = end - start;
var ratio = distance > 0 ? (window.scrollY - start) / distance : 1;
ratio = Math.min(Math.max(ratio, 0), 1);
bar.style.transform = 'scaleX(' + ratio + ')';
ticking = false;
}
function requestUpdate() {
if (!ticking) {
ticking = true;
window.requestAnimationFrame(update);
}
}
window.addEventListener('scroll', requestUpdate, { passive: true });
window.addEventListener('resize', requestUpdate);
window.addEventListener('load', requestUpdate);
update();
})();
計算の考え方は次のとおりです。本文の上端がページ上部に来た位置を start、本文の下端が画面の下端に来た位置を end とし、現在のスクロール位置がその間のどこにあるかを割合にしています。本文が画面より短い場合は distance が0以下になるので、最初から読み終えた扱い(1)にしています。
スクロールイベントは1秒間に何十回も発生します。毎回計算すると処理が詰まるため、requestAnimationFrame で画面の描画1回につき1度だけ計算するようにしています。{ passive: true } は、このイベント処理がスクロールを止めないことをブラウザに伝え、スクロールの引っかかりを防ぐ指定です。
load イベントでも再計算しているのは、画像の読み込みで本文の高さが後から変わるためです。遅延読み込みの画像が多い記事では、スクロール中にも高さが変わりますが、スクロールのたびに本文の位置と高さを測り直しているので追従します。
本文のクラス名はテーマによって異なります。バーが動かないときは、開発者ツールで投稿本文を囲んでいる要素のクラス名を調べ、querySelector の指定を書き換えてください。
ステップ4:投稿ページでだけ読み込む
<?php
// 子テーマの functions.php に追記
add_action( 'wp_enqueue_scripts', 'mychild_enqueue_reading_progress' );
function mychild_enqueue_reading_progress() {
if ( ! is_singular( 'post' ) ) {
return;
}
$dir = get_stylesheet_directory();
$uri = get_stylesheet_directory_uri();
wp_enqueue_style(
'mychild-reading-progress',
$uri . '/assets/css/reading-progress.css',
array(),
filemtime( $dir . '/assets/css/reading-progress.css' )
);
wp_enqueue_script(
'mychild-reading-progress',
$uri . '/assets/js/reading-progress.js',
array(),
filemtime( $dir . '/assets/js/reading-progress.js' ),
true
);
}
HTMLを出力する条件(ステップ1)と、CSS・JavaScriptを読み込む条件をそろえておくと、不要なページでファイルを読み込まずに済みます。get_stylesheet_directory_uri() は子テーマのURLを返す関数で、親テーマを指す get_template_directory_uri() と取り違えるとファイルが404になります。
CSSだけで作る方法との違い
最近のブラウザでは、CSSのスクロール連動アニメーション(animation-timeline)を使って、JavaScriptなしでバーを伸ばすこともできます。
@supports (animation-timeline: scroll()) {
.reading-progress__bar {
animation: reading-progress-grow linear both;
animation-timeline: scroll(root);
}
@keyframes reading-progress-grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
}
手軽ですが、この書き方はページ全体のスクロール量を基準にするため、コメント欄やフッターが長いサイトでは本文を読み終えてもバーが途中で止まって見えます。また、対応状況はブラウザによって差があり、@supports で囲んで非対応のブラウザでは何も起きないようにしておく必要があります(2026年10月現在)。本文基準の正確な表示を求めるなら、この記事のJavaScript方式のほうが扱いやすいでしょう。両方を同時に入れると表示がぶつかるので、どちらか一方を選んでください。
動作確認のチェックリスト
- 投稿ページを開き、スクロールに合わせてバーが左から伸びる
- 本文の最後まで読んだ時点で、バーが右端まで届く
- 一覧ページや固定ページにはバーが出ない
- ログイン中は管理バーの下にバーが表示される
- ヘッダーのリンクやメニューが、バーに邪魔されずクリックできる
キャッシュ系プラグインを使っている場合、変更が反映されないときはキャッシュを削除してから確認してください。
まとめ
- 読み進み度インジケーターは、
wp_body_openで出力したバーを、JavaScriptで計算した割合に合わせてtransform: scaleX()で伸ばして作ります。 - 計算の基準をページ全体ではなく投稿本文にすると、読み終えたときにバーが右端まで届く自然な動きになります。
requestAnimationFrameとpassive: trueでスクロール時の負担を抑え、管理バーや固定ヘッダーとの重なりはtopとz-indexで調整します。- バーは装飾なので
aria-hidden="true"で読み上げから外します。 - CSSだけの方法は手軽ですが、ページ全体が基準になる点とブラウザ対応に注意が必要です。