スクロールによってグローバルナビゲーション等が隠れるのを避けるために、ヘッダー要素を固定する「スティッキーヘッダー」をjQueryで実装します。
実際に完成したもの
コード等含め、下記サイトで確認できます。 https://codepen.io/itsumoonazicode/full/abwqNdB
実装したコードの流れ
HTML, CSSは先述のサイトからご確認ください。JavaScriptのコードに絞って詳述します。
変数を定義する
必要な要素を変数として定義します。いつも忘れるのですが、offset()
メソッドはドキュメントを起点として、指定した要素の位置を取得します。
参考:jQueryのoffset() で表示位置の取得と要素の移動
var $window = $(window), $main = $('main'), $header = $('.page-header'), headerOffsetTop = $header.offset().top, headerHeight = $header.outerHeight();
イベントを定義する
$window.on('load scroll', _.debounce(function() { // 処理を書く }, 50));
ヘッダーを上部に固定させたいのは、以下のタイミングです。
- 画面をスクロールしたことでヘッダーが隠れたタイミング
これを実現するために、スクロールイベントを定義します。合わせて、画面中腹でページがリロードされた場合などを考慮して、ロードイベントを定義します。(たとえば、アンカーリンクを経由してページを訪れると、いきなりページ中盤から表示されたりしますね。そういう場合でもヘッダーを上部に固定するためにロードもイベント対象に入れます。)
処理を間引く(debounce)
スクロールをイベント対象に入れて、その中でconsole.log($window.scrollTop())
と書いてみると、少しのスクロールだけでコンソールがログで埋め尽くされるのが分かるかと思います。イベント内に書かれた処理の内容によっては、ブラウザの処理が重くなる場合が出てきます。それを防ぐために、イベントの実行頻度を下げる処理を入れています。
参考:lodash の debounce や throttle で簡単に負荷対策 - Qiita
$window.on('load scroll', _.debounce(function() { // 処理を書く }, 50));
スクロール量とヘッダーの初期位置を比較する
if($window.scrollTop() > headerOffsetTop) { // 処理を書く }
ヘッダーが隠れるタイミングで、ヘッダーを上部固定にするためにスクロール量とヘッダーの初期位置を比較します。こちらの記事「jQueryのoffset() で表示位置の取得と要素の移動」内【offset() の起点】を合わせて見てもらうとわかりやすいのですが、要素の(今回はヘッダー)offset().top
をスクロール量が上回ると、要素の上部分が画面から隠れることになります。
ヘッダーを上部に固定したいのはまさにそのタイミングです。なので、条件分岐として『スクロール量 > ヘッダーの初期位置』を最初に書きます。
スクロール量が上回る場合、ヘッダーを上部に固定する
※CSSはこちらのページからご確認ください。
https://codepen.io/itsumoonazicode/full/abwqNdB
if($window.scrollTop() > headerOffsetTop) { $header.addClass('sticky'); $main.css('margin-top', headerHeight); }
スクロール量がヘッダーの初期位置を上回ったタイミングで、固定用のクラスを付与します。
$main.css('margin-top', headerHeight);
としているのは、ヘッダーを固定するために使うCSS position: fixed;
が関連しています。これを指定された要素は、レイアウトから外されてしまいます。そのため、ヘッダーが上部に固定された瞬間に、元々ヘッダーの下にあったコンテンツが一気に上へせり上がることになり、コンテンツがヘッダーに隠れてしまいます。それを防ぐために、ヘッダーを上部に固定するタイミングで、ヘッダーの高さ分の余白をヘッダーの次に続く要素に与えています。
それ以外の場合
if($window.scrollTop() > headerOffsetTop) { // 当てはまるときの処理を書く } else { $header.removeClass('sticky'); $main.css('margin-top', '0'); }
スクロール量がヘッダーの初期位置を下回るタイミングで、上部固定用クラスを削除します。合わせて、ヘッダーの次に続く要素に付与した余白も0に変更します。
これで完成です。
おわりに
どうもdebounceの指定方法がおかしいのか、上手く上部固定が働きません…。実際にこのコードを使う場合には該当箇所からdebounceは外す必要がありますね。。ここを修正できたら再度記事を書こうと思います。