モバイルブラウザでメニュー表示時にメニューをスムーズにスクロールさせ、コンテンツは位置固定で留める

  • 1
    Like
  • 0
    Comment

iPhone Safari などのモバイルブラウザで、位置固定されたヘッダの中にあるハンバーガーメニュー(これの良し悪しは置いといて)などにより、メニューがシュッて出てくるウェブページはこのご時世よく見かけますが、以下のことで不満があったりします。

  • メニュー内のスクロールがカクカクする
  • スクロールしたらメニューが途中で途切れる
  • スクロールしたらコンテンツまで一緒にスクロールされてしまう
  • メニューを開いたらコンテンツが一番上まで戻ってしまう
  • 進んだ先のページを元に戻ってメニュー内の別の項目へ行きたいのに再度メニューを開かないといけない

この不満を解消すべくの方法です。あくまでもこの不満は個人的なものなので、みなさんの欲求には当てはまらないかもしれません。

HTML

ヘッダとメニュー以外を #field でくくります。

index.html
<input id="chk_nav" type="checkbox" />
<header>
    <h1>ヘッダ</h1>
    <label for="chk_nav">MENU</label>
</header>
<nav id="nav_global">
    <ul>
        <li>
            <a href="?test=00">メニュー項目00</a>
        </li>
        <li>
            <a href="?test=01">メニュー項目01</a>
        </li>
        <li>
            <a href="?test=02">メニュー項目02</a>
        </li>
    </ul>
</nav>
<div id="field">
    <section>#field の中の内容01</section>
    <section>#field の中の内容02</section>
    <section>#field の中の内容03</section>
</div>

Sass

メニューの開閉は input#chk_nav で管理するので、:checked を使ってシュッと出します。
メニューの背景色はメニュー本体である #nav_global に付けるとメニュー内容が少ない場合にブサイクなので、#field:after で色を付け、一緒にシュッと出します。
開いた際に #field は .nav_open が付いて 100% * 100% の固定となりスクロール不可になり、#nav_open も .nav_open が付いて position:relative となってスクロールがスムーズにできるようになります。

spmenu.sass
#nav_global, #field:after
    +transition-property(transform)
    +transition-duration(.3s)
    +transition-delay(0s)
    +transition-timing-function(ease)
#nav_global
    width: 100%
    position: fixed
    top: 50px
    left: 0
    z-index: 99
    +transform(translateX(100%))

    ul
        margin: 0
        padding: 0

        li
            display: block
            border-bottom: 1px solid #fff

            a
                padding-left: 10px
                display: block
                line-height: 50px
                color: #fff
                text-decoration: none
    &.nav_open
        position: relative
#field
    &:after
        content: ''
        width: 100%
        height: 100%
        display: block
        position: fixed
        top: 0
        left: 0
        z-index: 98
        background-color: rgba(#000, .7)
        +transform(translateX(100%))
    &.nav_open
        width: 100%
        height: 100%
        position: fixed
        top: 0
        left: 0
        z-index: 0
        overflow: hidden
#chk_nav
    display: none

    &:checked+header
        &+#nav_global, &+#nav_global+#field:after
            +transform(translateX(0))

CoffeeScript

Sass で書いた通り、メニューの管理は #chk_nav でするので、.prop('checked') で判断します。
開いた際は、現在のスクロール位置を取得し、#field へ .nav_open を付けて固定化した上でこれをスクロールし、そのままの位置であるかのように見せます。
シュッと出た後を見計らって(Sass で .3s としているので、350ms としてます) #global_nav へ .nav_open を付けてスクロールできるようにします。
閉じた際は、これらの class を解き放ち、開いた際に取得したスクロール位置に戻してやります。

spmenu.coffee
spmenu =
    positions: 0
    fire: ($this) ->
        if $this.prop('checked')
            @positions = $(document).scrollTop()
            $('#field').addClass('nav_open').scrollTop(@positions)
            $('#nav_global')
                .delay(350).queue ->
                    $(this).addClass('nav_open').dequeue()
                    $('html, body').scrollTop(0)
                    return
        else
            $('#nav_global').removeClass('nav_open')
            $('#field').removeClass('nav_open')
            $('html, body').scrollTop(spmenu.positions)
        return
$ ->
    $('#chk_nav').on 'change', ->
        spmenu.fire($(this))
        return
    return

生成された JavaScript も置いておきます。

spmenu.js
(function() {
  var spmenu;

  spmenu = {
    positions: 0,
    fire: function($this) {
      if ($this.prop('checked')) {
        this.positions = $(document).scrollTop();
        $('#field').addClass('nav_open').scrollTop(this.positions);
        $('#nav_global').delay(350).queue(function() {
          $(this).addClass('nav_open').dequeue();
          $('html, body').scrollTop(0);
        });
      } else {
        $('#nav_global').removeClass('nav_open');
        $('#field').removeClass('nav_open');
        $('html, body').scrollTop(spmenu.positions);
      }
    }
  };

  $(function() {
    $('#chk_nav').on('change', function() {
      spmenu.fire($(this));
    });
  });

}).call(this);

サンプルを置いておきます