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

前言

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

導讀

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

Scroll bouncing

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

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

背景

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

場景回顧

假如入咱們有下面這個頁面,底部有一個固定且不能移動的footer,同時頁面其餘內容能夠滾動。看起來以下: 若是是在非觸摸屏和觸摸板的Firefox和其餘瀏覽器上,表現是符合預期,可是當咱們使用mac上的chrome時,當用觸摸板scroll到最下部時,事情就有點不同了。 jquery

雖然設置了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定位而且在滾動過程當中不移動。ios

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

解決方案

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

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

只用css、html來解決

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

使用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也會跟着一塊兒滑動。。。當滑動很快的時候表現簡直是災難。以下圖 app

此外,另外一個不想看到的狀況也出現了,當滑來滑去的嘗試的時候,發現此時的滑動性能有點差。
由於咱們設置滑動容器的高度爲它自己的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 6三、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,提供了比較豐富滑動橡皮筋特效的解決思路。才疏學淺,有些翻譯不到位的地方多請指正,詳情異步原文

相關文章
相關標籤/搜索