LoginSignup
3
3

More than 5 years have passed since last update.

jQueryとcssで縦長メニューを画面内に収める(スクロール&追随)

Last updated at Posted at 2018-01-17

こんな画面でサイドメニューがものすごく縦長に…

scrollmenu.PNG
メニューが多くなってなんだかやたらと縦長になってしまったときに。
まぁ、アコーディオン化するライブラリとか使えばいいかもしれませんが、自分で実装してみました。

メニューのスクロールONとスクロールバーのデザイン

まずはmenu部分の高さを表示高に固定し、画面からはみ出たらスクロールバーを出現させます。
ついでにスクロールバーを色々デザインしたいところですが、ブラウザによって可否はそれぞれでした。

表示高に合わせる.js
$(function(){
    //メニューエリアの高さを調整
    function adjustMenuHeight() {
        $('#menu').height($(window).height());
    }

    $(window).on('load resize', function() {
        adjustMenuHeight();
    });
});
スクロールバー.css
#menu {
    overflow: auto; /* スクロールバー必要なら表示 */
    -ms-overflow-style: -ms-autohiding-scrollbar; /* IE用:スクロールバー自動非表示 */
}

/* Chrome,Safari用:スクロールバーデザイン */
#menu::-webkit-scrollbar {
    width: 4px;
    -webkit-border-radius: 3px;
    border-radius: 3px;
}
#menu::-webkit-scrollbar-thumb {
    background: #abcdef;
    -webkit-border-radius: 3px;
    border-radius: 3px;
}
/* FireFoxはデザインできないらしい */

Chromeはwebkitで色々好みに合わせたデザインができました。
IEは自動非表示ぐらいでしょうか。
FireFoxはデザインできないようです…。

(参考)
CSSでスクロールバーをカスタマイズ
スクロールさせる領域だけどスクロールバーは非表示にしたい。
-ms-overflow-style

コンテンツ側のスクロールがあるので…

さて、contents側が長くなるページもあるのでその場合はメニューを追随させて常に画面に表示させるようにしたいと思います。

ついてくるメニュー.js
$(function(){
    //コンテンツスクロールに追随するメニュー
    var fix = $('#menu'); //固定する要素
    var main = $('#contents'); //固定する要素を収める範囲
    var sideTop = fix.offset().top;
    var fixTop = fix.offset().top;
    var mainTop = main.offset().top;
    $(window).on('scroll', function() {
        fixTop = fix.css('position') === 'static' ? sideTop + fix.position().top : fixTop;
        var fixHeight = fix.outerHeight(true);
        var mainHeight = main.outerHeight();
        var winTop = $(window).scrollTop();
        if (winTop + fixHeight > mainTop + mainHeight) {
            fix.css({
                position: 'absolute',
                top: mainHeight - fixHeight
            });
        } else if (winTop >= fixTop) {
            fix.css({
                position: 'fixed',
                top: 0
            });
        } else {
            fix.css('position', 'static');
        }
    });
});

ここはもうほぼ参考サイトのまんまです…
サイドバーをある範囲内で固定してスクロールに追尾させるjQuery

ヘッダとフッタがあった

headerfooterがあって、contentsのスクロール状態によって画面上に出現したり見えなくなったりするのでそれも考慮してメニューの高さを決める必要がありました。

上で書いたmenuの高さを求める箇所をいじります。

高さ可変.js
$(function(){
    //メニューエリアの高さを調整
    function adjustMenuHeight() {
        //header,footerが表示されているかで変動
        var headerPos = $('#header').offset().top + $('#header').height();
        var footerPos = $('#footer').offset().top - $(window).height();
        var headerH = 0;
        var footerH = 0;
        //headerが見えている場合
        if ($(window).scrollTop() < headerPos) {
            headerH = $('#header').height() - $(window).scrollTop();
        }
        //footerが見えている場合
        if ($(window).scrollTop() > footerPos) {
            footerH = $(window).scrollTop() - footerPos;
        }
        var h = $(window).height() - headerH - footerH;
        $('#menu').height(h);
    }
});

また、contentsのスクロールに合わせてheaderfooterの表示を判断するため、先ほどの$(window).on('scroll', function() {...}の終わりでもこの関数を呼びます。

(参考)
【jQuery】スクロールする要素内で指定要素が表示状態かを判定する

選択したメニューの位置に移動させたい

ついでに、menuを選択するとページ読み込みが走ってmenuは常に先頭の位置が表示されているのも不親切なので、選択したメニューの位置まで自動スクロールするようにします。

なお、「選択したメニュー」を取る方法は色々あると思うので割愛しますが(class付与してるとか? 自分のケースではbackground-colorだけが判別可能な要素でした…)、ポイントは以下です。

選択位置まで自動スクロール.js
$(function(){
    $(window).on('load', function() {
        $('#menu').animate({scrollTop:$(選択メニュー).offset().top - $('#menu').offset().top});
    });
});

(参考)
たった1行!jQueryで指定箇所までアニメーションつきで自動スクロールさせる方法

jsまとめ

menu.js
$(function(){
    //メニューエリアの高さを調整
    function adjustMenuHeight() {
        //header,footerが表示されているかで変動
        var headerPos = $('#header').offset().top + $('#header').height();
        var footerPos = $('#footer').offset().top - $(window).height();
        var headerH = 0;
        var footerH = 0;
        //headerが見えている場合
        if ($(window).scrollTop() < headerPos) {
            headerH = $('#header').height() - $(window).scrollTop();
        }
        //footerが見えている場合
        if ($(window).scrollTop() > footerPos) {
            footerH = $(window).scrollTop() - footerPos;
        }
        var h = $(window).height() - headerH - footerH;
        $('#menu').height(h);
    }

    //コンテンツスクロールに追随するメニュー
    var fix = $('#menu'); //固定する要素
    var main = $('#contents'); //固定する要素を収める範囲
    var sideTop = fix.offset().top;
    var fixTop = fix.offset().top;
    var mainTop = main.offset().top;
    $(window).on('scroll', function() {
        fixTop = fix.css('position') === 'static' ? sideTop + fix.position().top : fixTop;
        var fixHeight = fix.outerHeight(true);
        var mainHeight = main.outerHeight();
        var winTop = $(window).scrollTop();
        if (winTop + fixHeight > mainTop + mainHeight) {
            fix.css({
                position: 'absolute',
                top: mainHeight - fixHeight
            });
        } else if (winTop >= fixTop) {
            fix.css({
                position: 'fixed',
                top: 0
            });
        } else {
            fix.css('position', 'static');
        }

        adjustMenuHeight();
    });

    $(window).on('load resize', function() {
        adjustMenuHeight();
    });
    $(window).on('load', function() {
        //選択メニューへスクロール移動
        $('#menu').animate({scrollTop:$(選択メニュー).offset().top - $('#menu').offset().top});
    });
});

20180510追記:コンテンツの横スクロールがある場合

contents部分が縦だけでなく横にも長くて横スクロールがある場合(これはこれでダサいというのは別議論として)、
このままだと横スクロールしてもmenuは画面左に固定されたままです。
ここでは横スクロールの場合はmenuも横に(画面外に)スクロールさせてみます。

縦は固定で横にはスクロール.js
    $(window).on('scroll', function() {
        ......
        if (winTop + fixHeight > mainTop + mainHeight) {
            ......
        } else if (winTop >= fixTop) {
            //Left位置を取得
            var mainScrollLeft = parseFloat($(window).scrollLeft()) * -1;
            fix.css({
                position: 'fixed',
                top: 0,
                left: mainScrollLeft
            });
        } else {
            ......
        }
        ......
    });

fixのときにleftをセットしてあげます。
marginなど計算する必要があれば計算します。(ここではしていませんが、そのためにparseしています)

(参考)
fixedの要素も横方向にはスクロールして欲しいという想い

以上です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3