スクロールの制御 - 下にスワイプして更新やオーバーフロー効果のカスタマイズ

要約

CSS overscroll-behavior プロパティを使用すると、デベロッパーは、コンテンツの上部または下部に到達したときのブラウザのデフォルトのオーバーフロー スクロール動作をオーバーライドできます。ユースケースとしては、モバイルでのスワイプで更新機能の無効化、オーバースクロール グロー効果とラバーバンド効果の削除、モーダル / オーバーレイの下にあるページ コンテンツのスクロールの防止などがあります。

背景

スクロール境界とスクロール チェーン

Chrome Android でのスクロール チェーン。

スクロールは、ページを操作する最も基本的な方法の一つですが、ブラウザのデフォルトの動作が特殊なため、特定の UX パターンへの対応が難しい場合があります。たとえば、ユーザーがスクロールする必要がある多数のアイテムを含むアプリドロワーを考えてみましょう。下部に到達すると、消費するコンテンツがなくなるため、オーバーフロー コンテナのスクロールが停止します。つまり、ユーザーが「スクロール境界」に到達したときです。しかし、ユーザーがスクロールを続けるとどうなるかに注目してください。ドロワーの背後にあるコンテンツがスクロールを開始します。スクロールは親コンテナ(この例ではメインページ自体)によって引き継がれます。

この動作はスクロール チェーンと呼ばれ、コンテンツをスクロールする際のブラウザのデフォルトの動作です。多くの場合、デフォルトは非常に優れていますが、望ましくない場合や、予期しない場合もあります。アプリによっては、ユーザーがスクロール境界に達したときに、異なるユーザー エクスペリエンスを提供したい場合があります。

スワイプで更新する効果

スワイプで更新は、Facebook や Twitter などのモバイルアプリで普及した直感的な操作です。ソーシャル フィードを下にドラッグして離すと、新しいスペースが作成され、より新しい投稿が読み込まれます。実際、この UX は非常に人気があり、Android 版 Chrome などのモバイル ブラウザでも同じ効果が採用されています。ページの上部で下にスワイプすると、ページ全体が更新されます。

Twitter のカスタム スワイプで更新
。PWA でフィードを更新するときに使用します。
Android 版 Chrome のネイティブの「下にスワイプして更新」操作
では、ページ全体が更新されます。

Twitter の PWA のような状況では、ネイティブの「スワイプで更新」アクションを無効にすることが理にかなっている場合があります。その理由は、このアプリでは、ユーザーが誤ってページを更新しないようにする必要があります。また、更新アニメーションが 2 回表示される可能性もあります。また、ブラウザのアクションをカスタマイズして、サイトのブランディングに近づけることもできます。残念なことに、この種のカスタマイズは実現が困難でした。デベロッパーは、不要な JavaScript を記述したり、非パッシブ タッチリスナー(スクロールをブロックする)を追加したり、ページ全体を 100vw/vh の <div> に固定したり(ページのオーバーフローを防ぐため)することになります。これらの回避策は、スクロールのパフォーマンスに悪影響を及ぼすことが十分に文書化されています。

もっと改善できるはずです。

overscroll-behavior のご紹介

overscroll-behavior プロパティは、コンテナ(ページ自体を含む)をオーバースクロールしたときの動作を制御する新しい CSS 機能です。これを使用すると、スクロール チェーンのキャンセル、プルダウン更新アクションの無効化 / カスタマイズ、iOS でのラバーバンド効果の無効化(Safari が overscroll-behavior を実装している場合)などを行うことができます。最も優れている点は、overscroll-behavior を使用しても、ページのパフォーマンスに悪影響を及ぼさないことです。

このプロパティには次の 3 つの値を使用できます。

  1. auto - デフォルト。要素で発生したスクロールは、祖先要素に伝播される可能性があります。
  2. contain - スクロール チェーンを防止します。スクロールは祖先に伝播しませんが、ノード内のローカル効果が表示されます。たとえば、Android のオーバースクロール グロー効果や iOS のラバーバンド効果は、スクロール境界に達したことをユーザーに通知します。注: html 要素で overscroll-behavior: contain を使用すると、オーバースクロール ナビゲーション アクションが防止されます。
  3. 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="refre>she<r"
  div class=&qu><ot;l>oad<ing-bar"/div
  div>< cla>ss=<"loading-bar"></div>
  <div class="loading><-bar>&<quot>;/<div
  div class=&q><uot;loading><-bar&quo>t;</div
/>div

section id="inbox"!-- msgs --/section

script
  let _startY;
  const inbox = document.querySel>ector('#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.class<List.co>ntains('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 なしでは実現が難しかった最適なユーザー エクスペリエンスを提供できます。

デモを見る | ソース