
こんにちは。細田です。
今回はスクロール進捗を視覚的に表すリーディングプログレスバーを実装していきたいと思います。
実装をする方法は様々ありますが、今回はJavaScriptを使った実装方法を紹介していきたいと思います。
完成したコードはこちら
初期設定
まず、最初にプログレスバーを実装する各sectionの要素を取得します。
// ページ内のすべての <section> 要素を取得
let scrollLines = document.querySelectorAll("section");
次にスクロールの割合を設定するための要素を定義します。
// 各要素の最大スクロール割合を記録するための Map(キー: 要素, 値: 最大スクロール値)
let maxScrolledValues = new Map();
似たようなObjectもありますが、今回は文字列型(String型)以外も使いますので、Mapを使用したいと思います。
MapとObjectの違い参考
https://qiita.com/oharu121/items/25cd17d1bc0b261d2f2c
関数の設定
/**
* スクロール時に各セクションの進捗バーを更新する関数
*/
function updateProgressBars() {
scrollLines.forEach((scrollLine) => {
// 要素の現在の位置情報を取得
let rect = scrollLine.getBoundingClientRect();
// セクションの高さ
let scrollLineHeight = scrollLine.offsetHeight;
// 画面の高さ
let windowHeight = window.innerHeight;
// 要素の識別用 ID(そのままの要素)
let scrollLineId = scrollLine;
// セクションの上端が画面内に入ってからのスクロール割合を計算
let scrolled = ((windowHeight - rect.top) / scrollLineHeight) * 100;
// スクロール割合を 0% ~ 120% に制限
// (CSSで20%引いて発火のタイミングを調整するため)
let adjust = 120;
scrolled = Math.min(Math.max(scrolled, 0), adjust);
// 要素ごとの最大スクロール値を取得・更新
let currentMax = maxScrolledValues.get(scrollLine) || 0;
let newMax = Math.max(currentMax, scrolled);
maxScrolledValues.set(scrollLine, newMax);
// 現在表示されている要素のみ CSS カスタムプロパティを更新
if (rect.top < windowHeight && rect.bottom > 0) {
scrollLine.style.setProperty('--progressBar', `${newMax}%`);
}
});
}
次に関数の設定をやっていきたいと思います。
先ほど取得した各sectionをforEachで回していきます。
scrollLines.forEach((scrollLine){}
各要素の位置情報を取得していきます。
// 要素の現在の位置情報を取得
let rect = scrollLine.getBoundingClientRect();
次に要素の高さを取得します。(今回は各セクション)
// セクションの高さ
let scrollLineHeight = scrollLine.offsetHeight;
次に現在表示されているウィンドウ(ビューポート)の高さをピクセル単位で取得します
// 画面の高さ
let windowHeight = window.innerHeight;
次に各セクション(scrollLine)が画面内にどれだけスクロールされたかを「割合(%)」で求めます。
// セクションの上端が画面内に入ってからのスクロール割合を計算
let scrolled = ((windowHeight - rect.top) / scrollLineHeight) * 100;
次に値が 0 以上 adjust(0~120%) 以下に収まるように制限しています。
100%じゃなく120%の理由は下記で説明します。
let adjust = 120;
scrolled = Math.min(Math.max(scrolled, 0), adjust);
120%じゃない理由は100%にしてしまうと常に画面の下にくっついた状態になってしまいますので、120%までにしてcssで計算をその分引きます。
これにより画面の下20%分ズレるため、くっついた状態を回避できます。
%は好みの値で大丈夫です。
max-heightを設定しないとセクションを突き抜けてしまうので、最大値を設定お忘れなく。
最後のセクションはスクロール量足りなくなってしまうので、その後にコンテンツがないと80%で止まってしまいますので、注意が必要です。今回はフッターを配置してスクロール量を確保しています。
&::after {
height: calc(var(--progressBar, 0) - 20%);
max-height: 100%;
}
次に各要素(scrollLine)ごとに、スクロールされた最大の割合を記録します。
let currentMax = maxScrolledValues.get(scrollLine) || 0;
let newMax = Math.max(currentMax, scrolled);
maxScrolledValues.set(scrollLine, newMax);
1行ずつ詳しく解説していきます。
let currentMax = maxScrolledValues.get(scrollLine) || 0;
maxScrolledValues は Map で、各 section の 最大スクロール割合 を保存するために定義します。
.get(scrollLine) で scrollLine(= section 要素)に対応する最大スクロール値を取得します。
もし scrollLine にまだ値が記録されていなければ 0 を代入します。(|| 0)
let newMax = Math.max(currentMax, scrolled);
現在のスクロール割合 (scrolled) と、過去の最大値 (currentMax) を比較。
大きい方の値を newMax に設定。
これによりスクロールを戻しても、値が低くならないようにしてます。
次に要素が現在画面内にあるかどうかを判定します。
// 現在表示されている要素のみ CSS カスタムプロパティを更新
if (rect.top < windowHeight && rect.bottom > 0) {
scrollLine.style.setProperty('--progressBar', `${newMax}%`);
}
rect.top < windowHeight(画面の上に入っている)
rect.bottom > 0(下端がまだ見えている)
このif文を入れることで、画面内の要素だけ更新できるので、パフォーマンスが最適化されます。
これで各要素でスクロールした際に追従するプログレスバーの完成です。
今回は直線ですが、円形のプログレバーにしても面白と思います。
以上がプラグイン等を使わずにプログレスバーを実装する方法です。
何かの参考までになれば幸いです。
ではまたの機会に