iPhoneのブラウザでボヨンと跳ね返るやつが固定ナビを誤作動させるときの対策

丁寧モード
時短モード
答えだけわかればいい人は「時短モード」がおすすめ。記事中の「補足」を非表示にします。

サイトのグローバルナビの動きで、最初は上に表示しているけど、ページをスクロールすると引っ込むやつがありますよね。

あれが、iPhoneで意図しないときに出たり引っ込んだりするときの対策です。

↓修正前(ユーザー名、パスワードともに「a」)
https://mizy.sakura.ne.jp/scrap/demo/boyon_before/

上のサンプルをiPhoneで見て、スクロールで上端、下端に行ってみるとナビが変な動きをするのがわかります。

この場合のグローバルナビに期待する動きは

  • ページトップでは大きく表示
  • 下にスクロールすると隠れる
  • 上にスクロールすると縮小で表示

です。

上端に行ったときは大きくグローバルナビが表示されることを期待しますが、隠れるという現象が起きます。

また、下端までスクロールすると隠れたままでいてほしいナビが出てくるという現象も起きます。

なぜこんなことが起きるのか?

それはiPhoneのブラウザで上端・下端まで行ったときに起こる「跳ね返り」アニメーションが原因です。

上端まで戻ったときの跳ね返り=「下にスクロール」ということ。下にスクロールしたときは隠れる動きをするように作っているので隠れてしまう、下端まで行ったときの跳ね返りは「上にスクロール」するということなので隠れていたナビが出てくるという動きになってしまうのです。

今回のこの動きはJavaScriptで作られていました。

//スクロールしたときに
	$window.on('scroll', function(){
        
			// 現在のスクロール位置を取得
			window_position = $(this).scrollTop();

			// スクロール後の位置がスクロール前の位置より少なければ
			if (window_position <= start_position) {
					// ナビを表示する
					$header.removeClass("header--unpinned");
			// スクロール後の位置がスクロール前の位置より多ければ
			} else {
					// ナビを隠す
					$header.addClass("header--unpinned");
			}


			// スクロールし終わったらそこを次のスクロールの基準点にする。
			start_position = window_position;
	});

つまり、「ちょっとでも上にスクロールすればナビを表示し、ちょっとでも下にスクロールすればナビを隠す」という処理になっているということです。

上端までスクロールして跳ね返るというのは「上にスクロールしたあと、下にスクロールする」動きをしています。下にスクロールする動きで終わっているからナビが隠れてしまうのです。

下端はその逆ですね。

じゃあ、1pxでも動いたら、という条件を100pxぐらい動くまで処理をしないようにすればいいのかというと、それはそれでだめです。

それだと上端、下端以外のスクロール時にナビの動きがワンテンポもたつく印象になります。

なので、

  1. 上端、下端のときの処理
  2. 上端、下端以外のときの処理

これを分けることがポイントです。

「スクロール後の位置がスクロール前の位置より少なければナビを表示する」
という条件に
「上端でも下端でもない場合」という条件を加えます。

上端はスクロール量で言えば「0」の状態なので、window_position==0かどうかで判定できます。

下端までのスクロール量はJavaScriptの関数「document.documentElement.scrollHeight」で取得できます。

この値がページをスクロールしたときの「現在のスクロール量と一致(かそれ以下)になれば下端までスクロールしたと判定できます。

var scroll_position = window.scrollY + window.innerHeight;
var document_height = document.documentElement.scrollHeight;
if (scroll_position >= document_height) {
    var bottom_position = 1;
}else{
    var bottom_position = 0;
}

このように書けば、下端に来たときに変数bottom_positionが1になり、そうでなければ0になるので、bottom_positionが1か0かによって下端かどうかが判定できます。

「上端じゃない」は「window_position<0」、「下端じゃない」は「bottom_position==0」となります。

ただ、上端については跳ね返り分も含めて考えないといけません。何度か試してみた限りでは跳ね返る量はだいたい50px以下ぐらいなので「「window_position<50」」としておけば期待する動きになります。

これらを踏まえ、

if (window_position <= start_position) {

if ((window_position <= start_position&&bottom_position==0) || window_position<50) {

に変えます。

これで上端、下端で跳ね返ったときにナビの誤作動がなくなります。

↓修正後(ユーザー名、パスワードともに「a」)
https://mizy.sakura.ne.jp/scrap/demo/boyon_after/

下端の判定がおかしい

もし下端に来ても、下端の判定にならなかったり、下端に来ていないのに下端と判定されるようなことが起こる場合、下端のスクロール量を取得するタイミングが早すぎる可能性が高いです。

window.addEventListener('load', function() {


}

このコードを下端の位置を取得する処理を包むように追加してください。

これは「ページの要素が全部読み込まれたら(その中に書いてある処理を実行する)」という条件です。

これがないとページの読み込みが終わる前にJavaScriptが動き始める可能性があります。特に画像ファイルは読み込みに時間がかかります。読み込まれるまではその画像は高さゼロなので、そのときにページ全体の高さを取得するとあとで読み込まれて高さが出た画像の分、全体の高さが変わることになります。

スマホでconsole.logを出す方法

今回のバグはiPhoneでだけ起こるものでした。PCで再現できるバグならブラウザの開発者ツールにJavaScriptのconsole.log()を使い、原因追求がしやすいですが、スマホではできないと思っていたので最初諦めかけました。

が、スマホブラウザで確認できる方法があったんですね。

それは、スマホのChromeでURLに「chrome://inspect」と入力すること。

するとボタンが出てくるので押すとconsole.logで出した値がここに表示される状態になります。

たとえば今回の場合だとページをスクロールしたときに現在のスクロール量をconsole.logで表示する処理を入れておき、ブラウザの別タブでページをスクロールするとリアルタイムでその値がその画面に出てきます。

今回、iPhoneだけで起こるバグということで、iPhoneにあって他の環境にないものは跳ね返りだけだったので、なんとなくこの跳ね返りがスクロール量としてカウントされていることが原因では?と疑いはしたものの、まさかそんなことないよな、それだったらもっとメジャーなバグとして周知されているはず、とにわかに信じられませんでした。

が、この方法を使い、下にスクロールしたら「down」、上にスクロールしたら「up」とconsole.logで出すと、跳ね返ったときにしっかりそれが出力されていたので確信にいたりました。

画像右下のアイコンクリックで原寸大。
画像クリックで戻る。