スクロールしても固定されて追尾するサイドバー

スクロールしても付いてくるサイドバーを自分なりに使いやすく作ってみました。

$(document).ready() だと実行するタイミングが早くて、画像などの内容の読み込みが終わった状態の要素の縦幅を取得できない場合があるので $(window).load() を使うのがいいようです。

サイドバーの上基準に固定

  • シンプルに上固定するタイプ
  • メインコンテンツがサイドバーより短い場合は何もしない

デモページ

var fixedSidebar = (function() {
  var navi,
      wrap,
      wrap_scroll,
      fixed_start,
      fixpx_end_top;
  return {
    run : function() {
      // サイドバーの固定するレイヤー
      navi = $('.fixnav');
      // ラッパーのレイヤー
      wrap = $('.wrapper');
      this.refresh();
    },
    refresh : function() {
      navi.css({
        position : 'relative',
        top : 'auto'
      });
      var navi_top = navi.offset().top;
      var wrap_top = wrap.offset().top;
      // 開始位置:ナビのTOP
      fixed_start = navi_top - parseInt(navi.css('margin-top'));
      // スクロールする上限
      fixpx_end_top = wrap_top + wrap.outerHeight() - navi.outerHeight(true);
      wrap_scroll = fixpx_end_top;
      if(navi_top + navi.outerHeight(true) < wrap_top + wrap.outerHeight(true)) {
        $(window).off('scroll', _onScroll).on('scroll', _onScroll);
      } else {
        $(window).off('scroll', _onScroll);
      }
      $(window).trigger('scroll');
    }
  };
  function _onScroll() {
    var ws = $(window).scrollTop();
    if(ws > fixpx_end_top) {
      // 固定する上限
      navi.css({
        position : 'fixed',
        top : (fixpx_end_top - ws) + 'px'
      });
    } else if(ws > fixed_start) {
      // 固定中間
      navi.css({
        position : 'fixed',
        top : '0px'
      });
    } else {
      // 固定開始まで
      navi.css({
        position : 'relative',
        top : '0px'
      });
    }
  }
})();

$(window).on('load', function() {
  fixedSidebar.run();
});

最初スクリプト側でマージンをつけていましたが、あとから弄るときに面倒臭そうだったので画面端に固定して CSS 側で調整するようにしました。

サイドバーの下を基準に固定

上固定だとサイドバーが長い時一番下までスクロールしないと見えないので下固定にしました。

  • 画面からはみ出る場合下を基準に固定、画面より小さい場合は上を基準に固定
  • 折りたたみなどで動的に長さが変わった場合あとから簡単に補正

デモページ

var fixedSidebar = (function() {
  var navi,
      main,
      main_scroll,
      fixed_start,
      fixpx_top,
      fixpx_end_top;
  return {
    run : function() {
      // 固定するナビゲーションレイヤー
      navi = $('.fixnav');
      // メインのレイヤー
      main = $('.main');
      this.refresh();
    },
    // 基準になる数値の計算
    refresh : function() {
      navi.css({
        position : 'relative',
        top : 'auto'
      });
      // メインコンテンツとナビの上部
      var navi_top = navi.offset().top - parseInt(navi.css('margin-top'));
      var main_top = main.offset().top - parseInt(main.css('margin-top'));
      if(navi_top + navi.outerHeight(true) < main_top + main.outerHeight(true)) {
        // 開始時のTOP基準値
        fixpx_top = Math.max(navi.outerHeight(true) - $(window).height(), 0);
        // ウィンドウに固定レイヤーが収まる
        if($(window).height() > navi.outerHeight(true)) {
          // スクロール上限
          main_scroll = main_top + main.outerHeight(true) - $(window).height() - (navi.outerHeight(true) - $(window).height());
          // 開始位置:ナビのTOP
          fixed_start = navi.offset().top - parseInt(navi.css('margin-top'));
          // 終了時のTOP基準値
          fixpx_end_top = main_scroll;
        }
        // ウィンドウに固定レイヤーが収まらない
        else {
          // スクロール上限
          main_scroll = main_top + main.outerHeight(true) - $(window).height();
          // 開始位置:ナビのBOTTOMがウィンドウに表示されたら
          fixed_start = (navi.offset().top + navi.outerHeight(true)) - $(window).height() - parseInt(navi.css('margin-top'));
          // 終了時のTOP基準値
          fixpx_end_top = main_scroll - (navi.outerHeight(true) - $(window).height());
        }
        $(window).off('scroll', _onScroll).on('scroll', _onScroll);
      } else {
        $(window).off('scroll', _onScroll);
      }
      $(window).trigger('scroll');
    }
  };
  function _onScroll() {
    var ws = $(window).scrollTop();
    if(ws > main_scroll) {
      // 固定する上限
      navi.css({
        position : 'fixed',
        top : (fixpx_end_top - ws) + 'px'
      });
    } else if(ws > fixed_start) {
      // 固定中間
      navi.css({
        position : 'fixed',
        top : -fixpx_top + 'px',
      });
    } else {
      // 固定開始まで
      navi.css({
        position : 'relative',
        top : 'auto',
      });
    }
  }
})();

$(window).on('load', function() {
  fixedSidebar.run();
}).on('resize', function() {
  fixedSidebar.refresh();
});

ウィンドウや要素の高さが変わった場合はそのタイミングでfixedSidebar.refresh();すると補正します。

レスポンシブにも対応させようかと思ったんですが、スマホにこの機能は必要なさそうなのでfixedSidebar.run();付近でごにょごにょ処理するぐらいでもいいかも。