スマホで拡大操作時に固定ナビゲーションの固定化を解除する

スマホでの固定ナビゲーション
スマホでの固定ナビゲーション

以前に「スマホで固定メニューはそのままにコンテンツをピンチインできるようにする(メモ:未完)」という記事を書きましたが、そこから進んで現実的と思われる案を作りましたので、記事にしたいと思います。

なお、前回の記事の目的を果たした訳ではなく、別の方向に舵をきっています。

「ピンチアウトなどで拡大しても固定したナビゲーションは拡大されない」という仕組みではないので、その点は予めご了承ください。

構築環境
JavaScriptpure JavaScript

実現したいこと

実現したいことは以下の通りです。

  • スマホで上部に固定ナビをつける
  • ピンチ操作やダブルタップで拡大された時は固定ナビを消す
  • 拡大が解除されたらナビを固定位置に戻す
  • iPhoneでもAndroidでも仕組みを動作させる

スマホで固定ナビを付けた際、ピンチアウトやピンチインなどのピンチ操作やダブルタップでの拡大時に固定ナビまで拡大され、ページの位置(特にページ上部)で拡大を行うとナビで内容が隠れてしまう問題があります。

そのため冒頭の以前に書いた記事にあるように、スマホのピンチ操作やダブルタップで拡大した際には固定したナビを拡大させない(ように見せる)方法を探していました。

しかし、英語圏含めて探しても方法が見つからず、実現のための良い手段も思いつかなかったために諦めました。

結果、現実的な手段として「拡大した時に固定ナビを消す」という動作の実現を目指した次第です。

具体的には以下のページで紹介されていた方針ですすめました。

iOSでのuser-scalable=no

なお、以前は以下のコードで拡大防止ができたのですが、iOS10からはこの指定が効かなくなっているため使えません。


<meta name="viewport" content="user-scalable=no">

コード

以下が作成したコードです。


//固定ナビのid名にfixednavを設定すると過程
var obj  = document.getElementById('fixednav');
var cw   = document.body.clientWidth;
var flug = true;

//640px以下の時に動作させる場合
if( window.matchMedia('(max-width:640px)').matches ) {

window.addEventListener( 'resize', function() {
  var iw = window.innerWidth;
  resizeChangeFixedClass();
});

window.addEventListener( 'scroll', function() {
  var iw = window.innerWidth;
  scrollChangeFixedClass();
});

}

//removefixedというクラス名を付け外しして、fixedの状態をabsoluteなどに変更して固定状態を解除する
function scrollChangeFixedClass() {
  if( flug ) {
    flug = false;
    setTimeout( function() {
    if( iw !== cw ){
      obj.classList.add('removefixed');
    } else {
      obj.classList.remove('removefixed');
    }
   flug = true;
      return flug;
    }, 400);
  }
}

function resizeChangeFixedClass() {
  if( flug ){
  flug = false;
    setTimeout( function(){
    if( iw !== cw ){
      obj.classList.add('removefixed');
    }
   flug = true;
      return flug;
    }, 400);
  }
}

意図としては以下のようなものです。

  • 拡大時ではなく、スクロール時に値を取得して判定する
  • リサイズイベントが起きたら消す

ジェスチャーイベントなどで拡大や縮小が起こっているタイミングで正確な値がとれないなら、拡大縮小が起こった段階でナビを消し、別の正確に値を取れるタイミング(今回はスクロール時)でナビを元に戻そうという方向です。

スクロールさせないとナビが固定に戻らないのは難点ですが、拡大時にコンテンツ部分が隠れるよりはよいだろうと考えました。

なお、clientWidthinnerWidthの値を比較しているため、PCなどのzoomが発生しない状態では固定ナビはそのままとなります。

一応、これで意図した動作になったと思います。

window.matchMedia

今回の調査過程で初めて知ったのがwindow.matchMediaです。

詳細は以下のページをどうぞ。

こういう便利なメソッドがあったのですね…。

JavaScriptでもcssのメディアクエリと同じような感じでレスポンシブな仕組みが作れるのはとても助かります。

イベントの抑制

毎度ではありますが、スクロールなどのイベントを監視して関数を実行する場合、過剰に起こりうる実行回数を抑制する必要があります。

今回は以下のページで紹介されているコードを使わせて頂きました。

構築時に起こった問題

単に拡大しているかどうか、という判定に思いのほか難儀しました。

一応、調べると以下のような感じの判定方法はでてきます。


var innerWidth  = window.innerWidth;
var clientWidth = document.body.clientWidth;

if ( innerWidth === clientWidth ) {
 //値が同じなら拡大縮小していないみなして処理
} else {
 //値が違うなら拡大縮小しているとみなして処理
}

ただ、今回上記を試したところ、拡大していない元の状態になったことを検知できない場合が多々ありました。

以下のイベントで試してみましたが、ピンチ操作の仕方によっては、拡大や縮小したあとに取れた値が「変化後の値」ではなく「変化前の値(おそらくですが)」となっていました。

  • touchend
  • gesturechange
  • gestureend

他のサイトではできると書かれているようでしたので、コードの書き方が悪い可能性は十分にあり得ますが、何度か調整しても意図した動作にならなったため別の手段を検討しました。

拡大禁止にする方法(未確認)

以下のページに、touchendイベントを無効化して拡大を禁止する方法が紹介されています。

一応試したのですが、構造か書き方が悪いのか、動作させることが出来ませんでした。

仮に実現できたとしても、イベントの無効化がもたらす影響がどこにでるかわかりませんので(今は良くても今後など)、本番に導入するのは怖いかなとは思いますが。

結び

なんとか代替案が形になったかなと思います。

4人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄