要約
CSS の overscroll-behavior
プロパティを使用すると、コンテンツの最上部または最下部に達したときのブラウザのデフォルトのオーバーフロー スクロール動作をオーバーライドできます。ユースケースとしては、モバイルで「プルして更新」機能を無効にする、オーバースクロールのグロー効果やラバーバンド効果を削除する、ページ コンテンツがモーダルやオーバーレイの下にある場合にスクロールを防ぐ、などがあります。
背景情報
スクロール境界とスクロール チェーン
スクロールはページを操作する最も基本的な方法の 1 つですが、ブラウザのデフォルトの動作が奇妙であるため、UX パターンによっては対処が難しい場合があります。たとえば、ユーザーがスクロールしなければならない可能性のあるアイテムが多数あるアプリドロワーについて考えてみましょう。コンテナが一番下に達すると、消費するコンテンツがなくなるため、オーバーフロー コンテナはスクロールを停止します。つまり、ユーザーが「スクロール境界」に到達したとします。ただし、ユーザーがスクロールを続けるとどうなるかに注目してください。ドロワーの背後のコンテンツがスクロールし始めます。スクロールは親コンテナ(この例ではメインページ自体)に引き継がれます。
この動作はスクロール チェーンと呼ばれ、コンテンツをスクロールする際のブラウザのデフォルトの動作です。多くの場合、デフォルトだけで十分ですが、望ましくない場合もありますし、予期せぬ場合もあります。アプリによっては、ユーザーがスクロール境界にヒットしたときに異なるユーザー エクスペリエンスを提供する必要があります。
「スワイプして更新」効果
「スワイプして更新する」は、Facebook や Twitter などのモバイルアプリで一般的に使用されている直感的な操作です。ソーシャル フィードを下にスワイプして離すと、最新の投稿を読み込むための新しいスペースが作成されます。実際、この UX は非常に人気が高く、Android の Chrome などのモバイル ブラウザにも同じ効果が採用されています。ページの上部で下にスワイプすると、ページ全体が更新されます。
Twitter の PWA などの状況では、ネイティブの「プルして更新」アクションを無効にしたほうが良い場合があります。なぜでしょう。このアプリの場合、ユーザーが誤ってページを更新することは望ましくありません。また、二重更新アニメーションが表示される可能性もあります。または、ブラウザのアクションをカスタマイズして、サイトのブランドに合わせてより調整したほうがよい場合もあります。残念なことに、この種のカスタマイズは実現が面倒です。デベロッパーが不要な JavaScript を記述するか、非パッシブなタッチリスナーを追加する(スクロールをブロックする)か、ページ全体を 100vw/vh の <div>
で固定する(ページのオーバーフローを防ぐため)。これらの回避策は、スクロールのパフォーマンスに悪影響を及ぼすと十分に文書化されています。
もっと良くできるよ!
overscroll-behavior
のご紹介
overscroll-behavior
プロパティは、コンテナ(ページ自体を含む)をオーバースクロールしたときの動作を制御する新しい CSS 機能です。スクロール チェーンのキャンセル、pull して更新するアクションの無効化/カスタマイズ、iOS でのラバーバンド効果の無効化(Safari が overscroll-behavior
を実装している場合)などに使用できます。最も良い点は、冒頭で言及したハッキングのように、overscroll-behavior
を使用してもページ パフォーマンスに悪影響を及ぼすことはないことです。
このプロパティは次の 3 つの値を取ります。
- auto - デフォルト。要素から発生したスクロールは、祖先要素に伝播されます。
- Contain - スクロールのチェーンを防止します。スクロールは祖先には伝播されませんが、ノード内のローカルな影響は表示されます。たとえば、Android でのオーバースクロール グロー効果や、iOS のラバーバンド効果(スクロール境界に達したときにユーザーに通知するラバーバンド効果)があります。注:
html
要素でoverscroll-behavior: contain
を使用すると、オーバースクロール ナビゲーション アクションを回避できます。 - none -
contain
と同じですが、ノード内のオーバースクロール効果も防ぎます(Android のオーバースクロール グローや iOS ラバーバンドなど)。
overscroll-behavior
の使用方法について、いくつかの例を見てみましょう。
スクロールで固定位置の要素がエスケープされないようにする
チャットボックスのシナリオ
ページ下部の固定位置にあるチャットボックスについて考えてみましょう。チャットボックスは自己完結型のコンポーネントであり、背後にあるコンテンツとは別にスクロールすることを目的としています。ただし、スクロール チェーンにより、ユーザーがチャット履歴の最後のメッセージに到達するとすぐにドキュメントのスクロールが開始されます。
このアプリでは、チャットボックス内で発生したスクロールをチャット内に留めることをおすすめします。これを行うには、チャット メッセージを保持する要素に overscroll-behavior: contain
を追加します。
#chat .msgs {
overflow: auto;
overscroll-behavior: contain;
height: 300px;
}
基本的に、チャットボックスのスクロール コンテキストとメインページを論理的に分離します。最終的には、ユーザーがチャット履歴の最上部または最下部に到達しても、メインページは開いたままになります。チャットボックス内で始まったスクロールは反映されません。
ページ オーバーレイのシナリオ
「アンダースクロール」シナリオのもう一つのバリエーションは、固定位置のオーバーレイの背後でコンテンツがスクロールする場合です。overscroll-behavior
のプレゼントのお知らせです!ブラウザが利便性を出そうとすると
サイトがバグのように見えます
例 - overscroll-behavior: contain
ありとなしのモーダル:
「プルして更新」を無効にする
クリックして更新するアクションは、CSS の 1 行で無効にできます。ビューポートを定義する要素全体でスクロール チェーンが発生しないようにしてください。ほとんどの場合、<html>
または <body>
です。
body {
/* Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain;
}
この簡単な追加により、チャットボックスのデモにある 2 回クリックして更新するアニメーションを修正し、代わりにより見やすい読み込みアニメーションを使用するカスタム効果を実装できます。受信トレイが更新されると、受信トレイ全体もぼかし処理されます。
以下に、コード全体のスニペットを示します。
<style>
body.refreshing #inbox {
filter: blur(1px);
touch-action: none; /* prevent scrolling */
}
body.refreshing .refresher {
transform: translate3d(0,150%,0) scale(1);
z-index: 1;
}
.refresher {
--refresh-width: 55px;
pointer-events: none;
width: var(--refresh-width);
height: var(--refresh-width);
border-radius: 50%;
position: absolute;
transition: all 300ms cubic-bezier(0,0,0.2,1);
will-change: transform, opacity;
...
}
</style>
<div class="refresher">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
<section id="inbox"><!-- msgs --></section>
<script>
let _startY;
const inbox = document.querySelector('#inbox');
inbox.addEventListener('touchstart', e => {
_startY = e.touches[0].pageY;
}, {passive: true});
inbox.addEventListener('touchmove', e => {
const y = e.touches[0].pageY;
// Activate custom pull-to-refresh effects when at the top of the container
// and user is scrolling up.
if (document.scrollingElement.scrollTop === 0 && y > _startY &&
!document.body.classList.contains('refreshing')) {
// refresh inbox.
}
}, {passive: true});
</script>
オーバースクロール グロー効果とラバーバンド効果を無効にする
スクロール境界に達したときにバウンス効果を無効にするには、overscroll-behavior-y: none
を使用します。
body {
/* Disables pull-to-refresh and overscroll glow effect.
Still keeps swipe navigations. */
overscroll-behavior-y: none;
}
完全なデモ
まとめると、完全なチャットボックス デモでは、overscroll-behavior
を使用してカスタムの「プルして更新する」アニメーションを作成し、チャットボックス ウィジェットのエスケープによるスクロールを無効にします。これにより、CSS の overscroll-behavior
なしでは実現が難しかった最適なユーザー エクスペリエンスを実現できます。