iPhone Safari などのモバイルブラウザで、位置固定されたヘッダの中にあるハンバーガーメニュー(これの良し悪しは置いといて)などにより、メニューがシュッて出てくるウェブページはこのご時世よく見かけますが、以下のことで不満があったりします。
- メニュー内のスクロールがカクカクする
- スクロールしたらメニューが途中で途切れる
- スクロールしたらコンテンツまで一緒にスクロールされてしまう
- メニューを開いたらコンテンツが一番上まで戻ってしまう
- 進んだ先のページを元に戻ってメニュー内の別の項目へ行きたいのに再度メニューを開かないといけない
この不満を解消すべくの方法です。あくまでもこの不満は個人的なものなので、みなさんの欲求には当てはまらないかもしれません。
HTML
ヘッダとメニュー以外を #field でくくります。
<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 となってスクロールがスムーズにできるようになります。
#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 =
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 も置いておきます。
(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);