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

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

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

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

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

```html: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 となってスクロールがスムーズにできるようになります。

```sass: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 を解き放ち、開いた際に取得したスクロール位置に戻してやります。

```coffeescript: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 も置いておきます。

```js: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);
```

[サンプルを置いておきます](http://code.flugel.biz/spmenu/)