掌控捲動方式 - 自訂下拉即可重新整理和溢位效果

TL;DR

CSS overscroll-behavior 屬性可讓開發人員在到達內容頂端/底部時,覆寫瀏覽器的預設溢位捲動行為。用途包括停用行動裝置的下拉更新功能、移除過度捲動發光和橡皮筋效果,以及防止頁面內容在互動視窗/疊加層下方捲動。

背景

捲動邊界和捲動鏈結

Chrome Android 版的捲動鏈結。

捲動是與網頁互動最基本的方式之一,但由於瀏覽器有奇特的預設行為,某些使用者體驗模式可能難以處理。舉例來說,假設應用程式導覽匣包含大量項目,使用者可能需要捲動瀏覽。當使用者捲動到最底部時,溢位容器會停止捲動,因為沒有更多內容可供瀏覽。換句話說,使用者會到達「捲動邊界」。但請注意,如果使用者繼續捲動,會發生什麼情況。抽屜式導覽面板後方的內容開始捲動!捲動功能會由父項容器接管,在範例中就是主頁面本身。

這種行為稱為捲動鏈結,是瀏覽器捲動內容時的預設行為。預設值通常很不錯,但有時可能不符合需求,甚至出乎意料。某些應用程式可能會想在使用者到達捲動邊界時,提供不同的使用者體驗。

下拉更新效果

下拉更新是 Facebook 和 Twitter 等行動應用程式普及的直覺式手勢。在社群動態消息中向下拉動並放開,即可騰出空間載入最新貼文。事實上,這種特殊的使用者體驗非常受歡迎,因此 Android 版 Chrome 等行動瀏覽器也採用了相同的效果。在頁面頂端向下滑動,即可重新整理整個頁面:

Twitter 在 PWA 中重新整理動態消息時,會使用自訂的下拉更新
。
Chrome Android 的原生下拉更新動作
會重新整理整個頁面。

在 Twitter PWA 等情況下,停用原生下拉更新動作可能較為合適。這是因為在這個應用程式中,您可能不希望使用者不小心重新整理頁面。甚至可能看到雙重重新整理動畫!或者,您也可以自訂瀏覽器的動作,使其更符合網站的品牌形象。可惜的是,這類自訂功能很難實現。開發人員最後會編寫不必要的 JavaScript、新增非被動觸控事件監聽器 (會阻擋捲動),或將整個頁面固定在 100vw/vh <div> (防止頁面溢位)。這些解決方法對捲動效能有明確記載的負面影響。

我們一定可以做得更好!

隆重推出 overscroll-behavior

overscroll-behavior 屬性是一項新的 CSS 功能,可控制過度捲動容器 (包括頁面本身) 時的行為。您可以使用這個屬性取消捲動鏈結、停用/自訂下拉更新動作、停用 iOS 上的橡皮筋效果 (當 Safari 實作 overscroll-behavior 時) 等。最棒的是,使用 overscroll-behavior 不會像簡介中提到的駭客一樣,對網頁效能造成負面影響!

這個屬性有三個可能的值:

  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 程式碼,就能關閉下拉更新動作。只要防止在整個可視區域定義元素上進行捲動鏈結即可。在大多數情況下,這會是 <html> 或 <body>:

body {
  /* Disables pull-to-refresh but allows overscroll glow effects. */
  overscroll-behavior-y: contain;
}

只要加上這項簡單的設定,就能修正聊天盒示範中的雙重下拉更新動畫,並改為實作自訂效果,使用更簡潔的載入動畫。收件匣重新整理時,整個收件匣也會模糊處理:

變更前
變更後

以下是完整程式碼片段:

<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,就很難達成。

查看示範 | 來源