前言
本文翻自Scroll Bouncing On Your Websites,拜讀之後收穫頗多,結合自己的理解,將該文章翻成中文,一方面加深理解另一方面好文共享。
導讀
本文介紹了不同瀏覽器上彈簧滾動(即scroll bouncing)特效及實現,並回顧了網上幾種常見的解決方案,順便介紹了下近來實現的css屬性 overscroll-behavior。希望讀過之後能對構建和設計帶有fixed元素的頁面有所幫助
Scroll bouncing
彈簧特效(同樣被叫做滑動橡皮筋特效或彈性滾動),經常發生於下面的場景:
- 當滾動到頁面或者html元素的最上或者最下部的時候,在頁面或者元素回到頂部/底部之前(即你鬆開手指或者滑鼠之前),會短暫的看到空白區域的出現。
- 同樣的效果也可以在元素之間的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正在開發中,未來可期。
結束語
參考文章
- Momentum Scrolling on iOS Overflow Elements
- Scroll Bouncing On Your Websites
- MOMENTUM SCROLLING USING JQUERY
再次感謝原作者William Lim,提供了比較豐富滑動橡皮筋特效的解決思路。才疏學淺,有些翻譯不到位的地方多請指正,詳情非同步原文