[css]スマホ対応のスクロール禁止ギミック

自作できるのは嬉しい

そもそもの話

詳細はほぼ覚えてないんだけど、以前調べたときはiPhoneに効かないみたいな条件付ばっかりで、スマホに対応できんなら意味ないわっていう。

グローバルナビを設置するにはスマホのデザインだと狭すぎるのでアイコンをタップしたら表示みたいなのがよくある。それは普通にスライドさせて表示させたり、ページの上のレイヤーに固定表示したりで様々ある。

それだけの認識でもって好みのデザインを採用すればいいんだけど、ナビゲーション項目が多くて画面をぶち抜いた場合はどうすんのって話になる。position:fixed;とかで固定表示すると表示領域というか要素の高さが限られるんで、入れ子を噛ませてoverflow-y:auto;とかでスクロールできるようにしましょうねって対策を取る。それもまあ想定内。

oveflowで要素内のスクロールができました、良かったねで済まない。
ページ自体のスクロールのことがある。
ナビのスクロールが終わったらページ側のスクロールが始まるみたいな、二重構造だからしょうがないけど操作としては望まれていない動作がある。

だから、特定のコンテンツだけをスクロールする(=ページ自体をスクロールしない)状態がひつようになる。

で、それは調べ方が悪かったのもあるかもしれないけど、スマホで実現したいのにスマホが除外されるっていう本末転倒な話がありましたよっていうね。

参考

Webページでスクロールできないようにする - CSS Positionプロパティを利用 (CSS Tips)

メチャクチャ簡単だった。

方法

今までになく感動したので引用は放棄します。
URLに飛んで確認して下さい。

Webページでスクロールできないようにする - CSS Positionプロパティを利用 (CSS Tips)

classとかidでもってギミックを用意してjsで付与タイミングを操作したら完了。

最後に

調べてた当時はスクロール禁止はjsでどうのこうのみたいなのが多かったんです。手間がかかる上に条件付きってなんだよって話で。自分の無知もあるけど出来合モノのプラグインじゃないと結局導入できなかった。

それがCSSだけで実現できたってのが本当に嬉しい。
めっちゃスマート。
レスポンシブもほぼCSSで組み上げて、jsではclassの付与だけみたいな感じに進めてたんで今回のテーマにもあってた。最高。

ついでに

これを導入したときに問題が一つある。

ページをある程度スクロール下状態でメニューを表示してメニューを閉じたらページの一番初めに戻っちゃう。cssの設定的に道理に適った動作なんだけどユーザーはびっくりするし、使いづらいなってなる。

ページの先頭にメニューボタンを設置する場合は無視していい。

メニュー表示のボタンをposition:fixed;で設置するとかしてる場合はページの表示位置を維持するためのギミックを増やす必要がある。

ということで、以下の動作を追加する。

メニュー表示時
・表示領域の座標を取得
・座標をマイナス値に変換してbodyのtopに指定

メニュー非表示時
・bodyのtopの値をマイナス値にしてwindowの表示座標に指定
・bodyのtopを削除

座標を変数で格納できたらいいんだけどね。ボタンクリック時がトリガーだから都度リセットされちゃうんで、どっかでリレーさせて引っ張る必要がある。

$(function(){
    //header
    $('#btn_hNav').on('click',function(){
        var scrollHeight = window.pageYOffset;
        $(this).toggleClass('active');
        $('header .nav').fadeToggle();
        $('body').toggleClass('noscroll');
        if($('body').hasClass('noscroll')){
            $('body').css('top',-(scrollHeight));
        }else{
            var scrollPosition = $('body').css('top').replace(/[^0-9]/g, '');
            $(window).scrollTop(scrollPosition);
            $('body').css('top','');
        }
    })
    $(window).on('load resize', function(){
        w = $(window).innerWidth();
        if(w < 767.98){
            $('header .nav').hide();
        }else{
            $('#btn_hNav').removeClass('active');
            $('header .nav').show();
            $('body').removeClass('noscroll');
        }
    })
})

htmlタグとかclassとかはイメージができるものをつけてるんで、そこから想像してほしい。

20221219追記:関数化で時短を狙う

こんな感じで関数にする。

    //発火する度に切り替え
    function bodyNoscroll(){
        var scrollHeight = window.pageYOffset;
        $('body').toggleClass('noscroll');
        if($('body').hasClass('noscroll')){
            $('body').css('top',-(scrollHeight));
        }else{
            var scrollPosition = $('body').css('top').replace(/[^0-9]/g, '');
            $(window).scrollTop(scrollPosition);
            $('body').css('top','');
        }
    }
    //発火したら解除
    function bodyNoscrollRelease(){
        $('body').removeClass('noscroll');
        var scrollPosition = $('body').css('top').replace(/[^0-9]/g, '');
        $(window).scrollTop(scrollPosition);
        $('body').css('top','');
    }

例えばこんな感じで使う。

    $(window).on('load resize', function(){
        if(window.matchMedia('(max-width:1023.98px)').matches){
        }else{
            //PC表示になったら解除
            bodyNoscrollRelease();
        }
    })
    $('#nav_btn').on('click',function(){
            $('header nav').slideToggle('fast');
            //メニュー表示時に適用、非表示時は解除
            bodyNoscroll();
    })

PC/SPともに動作を同じにするなら振り分ける必要はないんだけど、レスポンシブで動作を切り替えるならtoggleだけじゃなくて、解除だけが機能するやつも欲しくなる。適用だけがほしい状況はあんまりない、割りとtoggleのやつで賄いきれてるので割愛。絶対に解除だけしたい場合はそうはいかなくて、ひょんな事で適用されちゃう場面が出てくるので使い回せない。それで、ヘッダーのメニュー表示だけを取り上げてもnoscrollを操作したい場面は複数出てくるので、だったら関数で管理したほうが楽だよねって感じに。

リンクをクリックしたらメニューを閉じてスムーススクロールさせたい場合

リンククリックの動作は他ページに遷移するばかりじゃない。スムーススクロールを仕込むことだってある。その場合、スクロール禁止解除との組み合わせをきちんとやっておかないとめんどいことが起きる。

    function bodyNoscrollRelease(){
        var scrollHeight = window.pageYOffset;
        $('body').removeClass('noscroll');
        if($('body').hasClass('noscroll')){
            $('body').css('top',-(scrollHeight));
        }
    }

    $('a[href^="#"]').not('a.notscroll').click(function(){
        bodyNoscrollRelease();
        var speed = 500;
        var headerHeight = 0;
        if(window.matchMedia('(max-width:1023.98px)').matches){
            headerHeight = $('header #h_main').innerHeight();
        }
        var href= $(this).attr("href");
        var target = $(href == "#" || href == "" ? 'html' : href);
        var position = target.offset().top - headerHeight;
        $("html, body").animate({scrollTop:position}, speed, "swing");
        return false;
    });

スムーススクロールの記述の中に解除を入れ込む。

そうしないとスクロール禁止中にスムーススクロールが発火して、結果動かないってことになってしまう。まず解除して、それからスクロールさせる。

コメント

タイトルとURLをコピーしました