【譯】談橡皮筋特效的解決方案

瀟湘待雨發表於2018-08-21

前言

本文翻自Scroll Bouncing On Your Websites,拜讀之後收穫頗多,結合自己的理解,將該文章翻成中文,一方面加深理解另一方面好文共享。

導讀

本文介紹了不同瀏覽器上彈簧滾動(即scroll bouncing)特效及實現,並回顧了網上幾種常見的解決方案,順便介紹了下近來實現的css屬性 overscroll-behavior。希望讀過之後能對構建和設計帶有fixed元素的頁面有所幫助

Scroll bouncing

彈簧特效(同樣被叫做滑動橡皮筋特效或彈性滾動),經常發生於下面的場景:

  1. 當滾動到頁面或者html元素的最上或者最下部的時候,在頁面或者元素回到頂部/底部之前(即你鬆開手指或者滑鼠之前),會短暫的看到空白區域的出現。
  2. 同樣的效果也可以在元素之間的CSS滾動捕捉(CSS scroll-snapping)中看到。 本文主要關注於第一種情況,換句話說就是滾動埠到達其滾動邊界時的場景
    對於Scroll bouncing的深入理解,可以幫助我們決定如何構建網頁和頁面如何滾動。

背景

當你不想看到fixed的元素跟著頁面移動時,彈簧特效就不那麼令人愉快了。例如:我們希望頁面上有位置固定的header和footer、或者需要一個fixed的選單、滾動過程中捕獲頁面的具體位置、不希望頂部或者底部有額外的滾動。這時候就需要我們去看一下有什麼方案去解決這類頁面頂/底部的彈簧特效了。

場景回顧

假如入我們有下面這個頁面,底部有一個固定且不能移動的footer,同時頁面其他內容可以滾動。看起來如下: 【譯】談橡皮筋特效的解決方案 如果是在非觸控式螢幕和觸控板的Firefox和其他瀏覽器上,表現是符合預期,但是當我們使用mac上的chrome時,當用觸控板scroll到最下部時,事情就有點不一樣了。 【譯】談橡皮筋特效的解決方案

雖然設定了footer為fixed=>bottom的css,但是這個橡皮筋特效確實有點猝不及防。
讓我們看看position:fixed到底幾個意思:
According to the CSS 2.1 Specification, when a “box” (in this case, the dark blue footer) is fixed, it is “fixed with respect to the viewport and does not move when scrolled.”
根據CSS 2.1 規範: 當一個box(這裡顯然是footer)被設定為fixed,它將根據viewport定位並且在滾動過程中不移動。

顯然上面的效果是預期之外的。
為了使文章更加完整,把在移動端Edge、移動端Safari和桌面Safari的效果都進行了嘗試,確實在firefox和chrome上的表現是不同的。在不同平臺上開發相同效果的滑動確實是一件挑戰性的事情。

解決方案

對於我們來說最先出來的想法肯定是簡單快捷的方式,那麼針對這種情況,首選當然是css來單獨處理。因此選擇下面的方式來嘗試。測試瀏覽器包括win10和mac上的chrome、firefox、safari以及Edge和移動端safari,瀏覽器的版本都是2018最新版本。頁面結構如下:

<html>
  <body>
    <div class="body-container">
      <div class="color-picker-main-container"> 
      </div>
    </div>
    <footer>
    </footer>
</body>    
複製程式碼

只用css、html來解決

一、絕對定位以及相對定位的方式

使用absolute來定位footer,然後html相對定位height100%,以便footer始終在下方固定,content的高度就是100%減去footer的高度。當然也可以設定padding-bottom來代替calc,同時設定body-containe為100%防止footer重複。語言比較蒼白,看程式碼就完了:

html {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

body {
  width: 100%;
  margin: 0;
  font-family: sans-serif;
  height: 100%;
  overflow: hidden;
}

.body-container {
  height: calc(100% - 100px);
  overflow: auto;
}

.color-picker-main-container {
  width: 100%;
  font-size: 22px;
  padding-bottom: 10px;
}

footer {
  position: absolute;
  bottom: 0;
  height: 100px;
  width: 100%;
}
複製程式碼

這種方式和原來fixed的方式幾乎一樣。差別在於該方式滑動的部分不再是整個頁面而是content內容,不包括footer。這種方式最大的問題在於移動端的safari上,不僅僅是content,footer也會跟著一起滑動。。。當滑動很快的時候表現簡直是災難。如下圖 【譯】談橡皮筋特效的解決方案

此外,另一個不想看到的情況也出現了,當滑來滑去的嘗試的時候,發現此時的滑動效能有點差。
因為我們設定滑動容器的高度為它本身的100%,這樣就阻礙了ios上的momentum-based scrolling,
這裡的momentum-based scrolling,我沒有很好的語言來翻譯,簡稱為阻尼滑動吧
簡單而言就是移動裝置上增加的一種旨在提升頁面滑動效能的功能,比較明顯的體現就是當你的手指輕觸觸碰裝置表面時,頁面自身開始滑動,當手指停止滑動之後頁面還會順勢滑動一會。更多瞭解請轉。我肯定是希望有這種效果的,所以要遠離設定滑動元素height100%。

在繼續其他的嘗試之前,我們先慢下來想一想當前的狀態。原先的fixed定位存在橡皮筋的問題,上面的將其轉換為absolute+relative的話沒有了阻尼滑動。如果想要阻尼滑動,那麼內容部分的height就不能設定為100%。那麼是否可以不去顯式設定height為100%呢。

html {
  width: 100%;
  position: fixed;
  overflow: hidden;
}

body {
  width: 100%;
  margin: 0;
  font-family: sans-serif;
  position: fixed;
  overflow: hidden;
}

.body-container {
  width: 100vw;
  height: calc(100vh - 100px);
  overflow-y: auto;
  // Use momentum-based scrolling on WebKit-based touch devices
  -webkit-overflow-scrolling: touch;
}

.color-picker-main-container {
  width: 100%;
  font-size: 22px;
  padding-bottom: 10px;
}

footer {
  position: fixed;
  bottom: 0;
  height: 100px;
  width: 100%;
}

複製程式碼

這裡設定html,body均為fixed、overflow: hidden。footer同樣為fixed。
在需要滾動的body-container內容區域設定其高度為100vh-footer的高度,
同時增加-webkit-overflow-scrolling: touch;開啟阻尼滑動支援。
效果會怎麼樣呢。 mac上的Chrome和Firefox和上一種方式標表現形式是一樣的,這種方式的優點就是不再需要100% height,
所以 momentum-based scrolling表現的還不錯,然而在Safari,footer不見了。。。
【譯】談橡皮筋特效的解決方案 在iOS的 Safari上,footer變短,並且底部有了個額外的間隔。同樣,當滾到底部的時候,滾動頁面的能力消失了。
【譯】談橡皮筋特效的解決方案

在上面程式碼裡-webkit-overflow-scrolling: touch;給指定元素增加 momentum-based scrolling 的能力。不過該屬性在MDN中標識是非標準的,相容性有待考慮,所以也只能拋棄它了。

另一種方案如下:

html {
  position: fixed;
  height: 100%;
  overflow: hidden;
}

body {
  font-family: sans-serif;
  margin: 0;
  width: 100vw; 
  height: 100vh;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

.color-picker-main-container {
  width: 100%;
  font-size: 22px;
  padding-bottom: 110px;
}

footer {
  position: fixed;
}

複製程式碼

這種方式在不同的桌面瀏覽器上表現是不錯的,阻尼滑動、footer固定並且不跟隨移動。但是這種方式的缺點在於在iOS Safari 上可以發現footer有輕微抖動
並且當你滑動的時候可以看到content在footer下面。

使用javascript

既然上面的方式都有些瑕疵,那麼我們還是試試js來解決吧。
首先宣告我不推薦並且建議儘量避免使用該方式。依據原作者的經驗,應該存在更為優雅和簡介的html+css方式。 不過已經花費了很多時間去解決該問題,去看看使用js是否有更好的方式也不會有什麼損失。

一種避免滑動彈簧的方式是阻止window或者document的touchmove或touchstart事件。思路是阻止外層window的tocuch事件,只允許content部分的touch。程式碼如下:

// Prevents window from moving on touch on older browsers.
window.addEventListener('touchmove', function (event) {
  event.preventDefault()
}, false)

// Allows content to move on touch.
document.querySelector('.body-container').addEventListener('touchmove', function (event) {
  event.stopPropagation()
}, false)
複製程式碼

我嘗試了很多方式盡力使滑動表現良好,阻止widow的touchmove和阻止document的沒什麼區別,我也嘗試使用touchstart和touchmove來控制滑動,
不過這兩種方式也沒什麼區別。後來發現出於效能的考慮,不應該這種方式來使用event.preventDefault(),應該設定將false作為passive的選項來設定。

// Prevents window from moving on touch on newer browsers.
window.addEventListener('touchmove', function (event) {
  event.preventDefault()
}, {passive: false})
複製程式碼

工具

另外可以使用iNoBounce來幫助自己,該庫目的就是解決ios上web應用滑動時的彈簧效應。需要提一下的時,使用該庫解決上面問題時要加上-webkit-overflow-scrolling。
另外我在結尾時提到的簡潔方法和其有異曲同工之妙,可以對比一下兩者。

Overscroll Behavior

嘗試那麼多方案之後,我發現了css的一個屬性overscroll-behavior,該屬性CSS屬性在2017年12月和2018年3月分別在Chrome 63、Firefox 59中實現。
根據mdn的定義:允許你控制瀏覽器的滑動溢位的行為--當到達滾動區域的邊邊界時會發生的行為。這就是最後的一種方案。
需要做的僅僅是在body設定overscroll-behavior:none,並設定footer為fixed,相比於沒有foter,整個頁面應用momentum-based scrolling是可以接受的。 更加客觀的是Edge正在開發中,未來可期。

結束語

參考文章

  1. Momentum Scrolling on iOS Overflow Elements
  2. Scroll Bouncing On Your Websites
  3. MOMENTUM SCROLLING USING JQUERY

再次感謝原作者William Lim,提供了比較豐富滑動橡皮筋特效的解決思路。才疏學淺,有些翻譯不到位的地方多請指正,詳情非同步原文

相關文章