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

要約

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

背景

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

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

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

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

プルして更新する効果

プルトゥ リフレッシュは、Facebook や Twitter などのモバイルアプリで広く使用されている直感的なジェスチャーです。ソーシャル フィードを下に引っ張って離すと、新しいスペースが作成され、最新の投稿が読み込まれます。実際、この特定の UX は非常に人気があり、Android 版 Chrome などのモバイル ブラウザでも同じ効果が採用されています。ページの上部を下にスワイプすると、ページ全体が更新されます。

Twitter のカスタム プルトゥ リフレッシュ
: PWA でフィードを更新するときに使用します。
Chrome Android のネイティブのプルして更新アクション
: ページ全体を更新します。

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 ありと 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 なしでは実現が難しかった最適なユーザー エクスペリエンスを提供できます。

デモを見る | ソース