プロのインターネット中毒者 • ゲーム愛好家 • 技術クリエイター
プロのインターネット中毒者 • ゲーム愛好家 • 技術クリエイター

iOSのSafariにおけるゴムバンドスクロールで学んだ6つのこと

iOSのSafariでのスクロール問題を解決するため、自分用のシンプルなテストページを作成せざるを得ませんでした!
このページは、皆様の便宜を図るため、熱意あふれるAIインターン生が英語から翻訳しました。彼らはまだ学習中なので、多少の誤りがあるかもしれません。正確な情報については、英語版をご覧ください。
ブログ iOSのSafariにおけるゴムバンドスクロールで学んだ6つのこと

このブログ記事は2015年6月に公開されたため、お読みいただいた時期によっては情報が古くなっている可能性があります。情報の正確性を保つため、これらの記事を常に最新の状態に保つことはできませんのでご了承ください。

iOS の Safari で最近起きたスクロールの問題を解決するために、自作のシンプルなテストページを作成し、インターネット上で見つけた情報や組み合わせをできるだけすべて試してみました。

このプロセスで私が発見したことの概要は以下のとおりです。

私のテストページ

私のテストページはかなりシンプルでした。ページを表す静的な div にモックデータを入れ、ページの上にオーバーレイとして配置される「メニュー」を表す固定の div を用意した、こんな感じでした。

iOSのSafariで開いたHTMLサイドメニューのスクリーンショット

このページの全ソースコードは、このブログ投稿の下部にあります。

デフォルトの挙動を理解する

最初にやったことは、iOS の Safari のデフォルト挙動を理解することでした。最も顕著な観察は、メニューを一番下までスクロールすると、ページ本文に「ラバー・バンド効果」が現れることです。これは Safari に組み込まれた仕様です。

iOSのSafariでページの一番下に現れる、問題のあるラバー・バンド効果をアニメーション付きで示したスクリーンショット

メニューの一番上までスクロールしたときにも、同じことが起こりました。

ページの最上部で発生する iOS Safari のラバー・バンド効果の問題を示すアニメーション付きスクリーンショット

ただし、最初に気づいた問題は、最上部または最下部までずっとスクロールし続けると、Safari が背景ページとメニューの両方にレンダリングの不具合を示し始めることです。

理由はよく分からないのですが、ゴムバンド機能に関係している気がします。

ページの下部に表示される、iOSのSafariで発生している不具合を示すアニメーション付きスクリーンショット

CSSルール「-webkit-overflow-scrolling」が何をするのかを理解する

次に行ったことは、メニューに -webkit-overflow-scrolling の CSS ルールを適用したことです。これにより モメンタムベースのスクロールを有効にします

以下の画像に示されているとおり、指でスワイプしてもスクロールは続きます。

iOSのSafariで発生する慣性スクロールの問題を紹介

ただし、最上部または最下部までスクロールすると、下図のとおりラバーバンド効果が発生します。

iOSのSafariでのモーメンタムスクロールとラバーバンド効果によるスクロールの問題を紹介

ラバーバンド効果が完了するまでお待ちください。

テスト中にも、ラバーバンド効果が進行している間は、アニメーションが完全に完了するまで、ユーザーは別の要素へフォーカスを移すことができません。

下の例では、まず背景ページをスクロールし、それから素早くメニューをスクロールしようとしますが、結局背景ページのスクロールを続けてしまいます。

iOSのSafariでは、背景ページが代わりにスクロールされていることを示しています。

ただし、エフェクトが終わるまで少し待てば、メニューのスクロールを始められます。

iOS Safari でリバウンド効果が完了するまでの待ち時間を紹介

バックグラウンドページのスクロールを防ぐ方法

テスト中にも、ラバーバンド効果が進行している間は、アニメーションが完全に終了するまで、ユーザーは別の要素にフォーカスを移すことができないことに気づきました。

この Stack Overflow の回答を基に、Stack Overflow の情報を活用すると、disable-scrolling を持つ要素が touchmove イベントをトリガーした際に、デフォルトのスクロール動作を行わないようにできます。

document.ontouchmove = function(event) { var isTouchMoveAllowed = true, target = event.target; while (target !== null) { if (target.classList && target.classList.contains('disable-scrolling')) { isTouchMoveAllowed = false; break; } target = target.parentNode; } if (!isTouchMoveAllowed) { event.preventDefault(); } };

続いて、ページの div 要素に disable-scrolling クラスを適用します:

<div class="page disable-scrolling">

この方法は一応動作しますが、より詳しい検証の結果、メニューの最上部または最下部に到達したときにバックグラウンドページが時々スクロールしてしまうことがあることが分かりました。

背景がまだスクロール中であることを示しています

背景ページの過度なスクロールを防ぐ方法

この fix を適用することで、オーバースクロールを防ぐことが可能になります。これは、要素が最上部または最下部までスクロールされたとき、スクロールが本文へと続かなくなることを意味します。

その結果、ボディのスクロールが制限され、リバウンド効果の発生も防がれます。

function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable"));

この JavaScript のスニペットは、要素が最上部にも最下部にも到達しないよう、常にわずか1ピクセルだけ離しておくという動作をします。

スクロール位置が最上部または最下部に到達しなければ、オーバースクロールは発生しません。

最上部にも最下部にも到達せず、オーバースクロールを防ぐデモ

バックグラウンドページへスクロールしすぎないようにする方法

メニューのリバウンド(ゴムのように伸びる)効果をなくしたい場合は、見た目のため、あるいは私の場合のように追加のレンダリング不具合を引き起こす場合は、-webkit-overflow-scrolling CSS ルールをただ削除してください。

ただし、以下のとおり、滑らかなスクロールの慣性も失われます。

iOS Safariでのリバウンドを防ぐための完全デモ

ページ全体のソースコード

このページの全ソースコードです。

<!-- License MIT, Author Special Agent Squeaky (specialagentsqueaky.com) --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="minimum-scale=1.0, width=device-width, maximum-scale=1.0, user-scalable=no, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style> .page{ font-size: 24px; overflow: scroll; } .menu{ position: fixed; top: 0; bottom: 0; left: 0; width: 80%; background: gray; z-index: 1; font-size: 10px; overflow: scroll; /* uncomment to get smooth momentum scroll, but also a rubber band effect */ /*-webkit-overflow-scrolling: touch;*/ } .menu-item{ padding: 10px; background: darkgray; font-size: 24px; } </style> </head> <body> <div class="menu scrollable"> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> </div> <div class="page disable-scrolling"> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </div> <script> document.ontouchmove = function(event) { var isTouchMoveAllowed = true, target = event.target; while (target !== null) { if (target.classList && target.classList.contains('disable-scrolling')) { isTouchMoveAllowed = false; break; } target = target.parentNode; } if (!isTouchMoveAllowed) { event.preventDefault(); } }; function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable")); </script> </body> </html>

Special Agent Squeaky 著。初版 2015-06-10。最終更新 2015-06-10。

📺 Squeakyの最新動画をチェック!

ライブ配信にリアルタイム字幕を簡単に追加する方法