こんにちは、株式会社ファストコーディングのBigViです。先日、仕事の参考になりそうなブログ記事を読んでいました。すごく長い記事で、途中で「これ、あとどれくらいあるんだろう」って思いました。。スクロールバーを見ればわかるけど、記事に集中していると見ないです。結局、「まだ半分もあるのか」と思って閉じてしまいました。
先日、お客さまから「ブログ記事の離脱率が高い」と相談がありました。記事の内容は良いのに、途中で離脱されてしまう。アクセス解析を見ると、記事の40%くらいのところで多くの人が離脱していました。「あとどれくらいか」がわからないと、人は不安になって離脱するみたいです。
私もよくあります。長い記事を読んでいて「まだ続くの?」と思って閉じてしまうこと。。
今回は「ページ上部に読了率のプログレスバーを表示する」という実装をしました。ユーザーが「あと半分くらいだ」とわかると、最後まで読んでくれる確率が上がります。
なぜプログレスバーが効くのか
長い記事で離脱される原因は、いろいろあります。でも意外と多いのは「終わりが見えない不安」です。
本を読むとき、残りのページ数が見えます。だから「あと少しだ」と思って読み続けます。でもWebの記事は、ブラウザのスクロールバーくらいしか手がかりがないです。スクロールバーは小さくて、記事に集中していると見ないです。
ページの上部に細いバーを表示するだけで、この問題を解決できます。ユーザーが意識しなくても、視界の端に「あと30%くらい」と見えると安心します。
ある案件で、最初はスクロールバーを太くする方法を試しました。でもデザインが崩れるし、スマホだと効果がなかったです。。プログレスバーを上部に固定する方法にしたら、デバイスに関係なく動きました。
実装方法
HTML構造
HTMLは1行だけです。bodyの先頭に入れます。
<div class="reading-progress" id="js-reading-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="読了率"></div>role="progressbar"とaria-labelはアクセシビリティのためです。スクリーンリーダーが「読了率」として認識してくれます。
CSSスタイル
バーはページの上部に固定します。高さは3pxで十分です。
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 0;
height: 3px;
background: #3b82f6;
z-index: 9999;
transition: none;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.reading-progress {
transition: none;
}
}pointer-events: noneをつけないと、バーの上にあるナビゲーションがクリックできなくなります。これは最初忘れていて、テストで気づきました。
z-index: 9999は高い値ですが、ヘッダーより上に表示したいので必要です。サイトによっては値を調整してください。
JavaScriptの実装
scrollイベントとrequestAnimationFrameを組み合わせます。
document.addEventListener('DOMContentLoaded', () => {
const bar = document.getElementById('js-reading-progress');
if (!bar) return;
let ticking = false;
function updateProgress() {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
if (docHeight <= 0) return;
const progress = Math.min((scrollTop / docHeight) * 100, 100);
bar.style.width = progress + '%';
bar.setAttribute('aria-valuenow', Math.round(progress));
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(updateProgress);
ticking = true;
}
}, { passive: true });
updateProgress();
});ポイントはtickingフラグです。scrollイベントは1秒間に何十回も発火します。毎回DOMを更新すると重くなります。requestAnimationFrameとtickingフラグで、画面の描画タイミングに合わせて1回だけ更新します。
{ passive: true }も大事です。ブラウザに「このイベントはpreventDefault()を呼ばないよ」と伝えます。スクロールの動きがスムーズになります。Lighthouseでも推奨されています。
CSS Scroll-driven Animationsによるプログレッシブエンハンスメント
対応ブラウザでは、JavaScriptを使わずにCSSだけでプログレスバーを作れます。animation-timeline: scroll()を使います。
@supports (animation-timeline: scroll()) {
.reading-progress {
animation: grow-progress linear;
animation-timeline: scroll();
}
@keyframes grow-progress {
from { width: 0; }
to { width: 100%; }
}
}@supportsで対応ブラウザだけに適用しています。対応ブラウザは拡大中で、最新の対応状況はCan I Useで確認してください。非対応ブラウザのために、JavaScriptのフォールバックも用意しておくと安心です。
CSSだけの方法は、メインスレッドを使わないのでパフォーマンスが良いです。対応ブラウザが増えたら、JavaScriptを完全に置き換えられます。
注意点
実装するときに気をつけたことが3つあります。
1つ目はprefers-reduced-motionです。プログレスバー自体は動かないので、animationの無効化だけで大丈夫です。バーの幅が変わるのは「動き」というより「状態の表示」なので、reduced-motionでも表示します。
2つ目はパフォーマンスです。scrollイベントの処理でoffsetHeightやgetBoundingClientRect()を何度も呼ぶと、レイアウトの再計算(Forced Reflow)が起きます。scrollHeightとinnerHeightの差分だけで計算すれば、Reflowを避けられます。
3つ目はモバイルのアドレスバーです。スマホのブラウザは、スクロールするとアドレスバーが隠れます。これでinnerHeightが変わります。でもscrollHeight - innerHeightで計算しているので、自動的に対応できます。
実際の結果
このお客さまのプロジェクトで、A/Bテストを4週間やりました。
| 指標 | バーなし(A) | バーあり(B) | 差分 |
|---|---|---|---|
| 記事の完読率 | 28.3% | 37.1% | +8.8pt |
| 平均スクロール深度 | 52.3% | 64.1% | +11.8pt |
| 記事ページの離脱率 | 67.8% | 59.2% | -8.6pt |
完読率が8.8ポイント上がりました。平均スクロール深度も11.8ポイント改善しています。「あとどれくらい」がわかると、最後まで読んでくれるみたいです。離脱率も8.6ポイント下がりました。ただし、これは1つのプロジェクトでの結果です。効果は記事の長さやジャンルによって変わります。
まとめ
長い記事での離脱は、内容の問題だけではないです。「あとどれくらいか」がわからない不安も大きな原因です。ページ上部に細いプログレスバーを表示するだけで、ユーザーに安心感を与えられます。
今回のポイントは以下の3つです:
requestAnimationFrameとtickingフラグで、スクロールイベントの処理を最適化する{ passive: true }とpointer-events: noneを忘れない。パフォーマンスと操作性に影響する- CSS Scroll-driven Animationsを
@supportsで段階的に導入できる。対応ブラウザではJavaScriptなしで動く
お客さまからは「たった3pxのバーでこんなに変わるとは思わなかった」と言ってくれました。。小さな工夫が大きな効果を生むこともあります。
株式会社ファストコーディングでは、こうした記事ページの離脱率改善やフロントエンドの実装サポートをしています。「記事を最後まで読んでもらいたい」「ユーザー体験を改善したい」という方は、お問い合わせフォームから気軽にご連絡ください。
※本記事は弊社外国人スタッフによる投稿です。言い回しや表現が不十分な個所がありますことご容赦いただきますようお願いいたします。

