原文鏈接: http://caibaojian.com/css-scroll.html
來自眾成翻譯的一篇:滑向未來(現代 JavaScript 與 CSS 滾動實作指南),里面解釋了現代前端中基于web標準的改善,各種流暢的滾動CSS和JavaScript代碼,這些特性將使你的頁面更平滑、美觀,
一些(網站)滾動的效果是如此令人著迷但你卻不知該如何實作,本文將為你揭開它們的神秘面紗,我們將基于最新的技術與規范為你介紹最新的 JavaScript 與 CSS 特性,(當你付諸實踐時,)將使你的頁面滾動更平滑、美觀且性能更好, 大多數的網頁的內容都無法在一屏內全部展現,因而(頁面)滾動對于用戶而言是必不可少的,對于前端工程師與 UX 設計師而言,跨瀏覽器提供良好的滾動體驗,同時符合設計(要求),無疑是一個挑戰,盡管 web 標準的發展速度遠超從前,但代碼的實作往往是落后的,下文將為你介紹一些常見的關于滾動的案例,檢查一下你所用的解決方案是否被更優雅的方案所代替,
消逝的滾動條
在過去的三十年里,滾動條的外觀不斷改變以符合設計的趨勢,設計師們為(滾動條的)顏色、陰影、上下箭頭的形狀與邊框的圓角實驗了多種風格,以下是 Windows 上的變化歷程:

(Windows 上的滾動條) 在2011年,蘋果設計師從 ios 上獲得靈感,為如何定義“美觀的”滾動條確定了方向,所有滾動條均從 Mac 電腦中消失,不再占據任何頁面空間,只有在用戶觸發滾動時(滾動條)才會重新出現(有些用戶會設定不隱藏滾動條),

(Mac 上的滾動條) 滾動條安靜地消逝并未引起蘋果粉絲的不滿,已經習慣了 iPhone 與 iPad 上滾動方式的用戶很快地習慣了這一設計,大多數開發人員與設計師都認為這是一個“好訊息”,因為計算滾動條的寬度可真是件苦差事,
然而,我們生活在一個擁有眾多作業系統與瀏覽器的世界中,它們(對于滾動)的實作各不相同,如果你和我們一樣是一名 Web 開發者,你可不能把“滾動條問題”置之不理,
以下將為你介紹一些小技巧,使你的用戶在滾動時有更好的體驗,
隱藏但可滾動
先來看看一個關于模態框的經典例子,當它被打開的時候,主頁面應該停止滾動,在 CSS 中有如下的快捷實作方式:
body {
overflow: hidden;
}
但上述代碼會帶來一點不良的副作用:

(注意紅色剪頭) 在這個示例中,為了演示目的,我們在 Mac 系統中設定了強制顯示滾動條,因而用戶體驗與 Windows 用戶相似, 我們該如何解決這個問題呢?如果我們知道滾動條的寬度,每次當模態框出現時,可在主頁面的右邊設定一點邊距, 由于不同的作業系統與瀏覽器對滾動條的寬度不一,因而獲取它的寬度并不容易,在Mac 系統中,無論任何瀏覽器(滾動條)都是統一15px,然而 Windows 系統可會令開發者發狂:

(“百花齊放”的寬度) 注意,以上僅是 Windows 系統下基于當前最新版瀏覽器(測驗所得)的結果,以前的(瀏覽器)版本(寬度)可能有所不同,也沒人知道未來(滾動條的寬度)會如何變化, 不同于猜測(滾動條的寬度),你可以通過 JavaScript 計算它的寬度(譯者注:實測以下代碼僅能測出原始的寬度,通過 CSS 改變了滾動條寬度后,以下代碼也無法測出實際寬度):
const outer = document.createElement('div');
const inner = document.createElement('div');
outer.style.overflow = 'scroll';
document.body.appendChild(outer);
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);
盡管僅僅七行代碼(就能測出滾動條的寬度),但有數行代碼是操作 DOM 的,(為性能起見,)如非必要,盡量避免進行 DOM 操作, 解決這個問題的另一個方法是在模態框出現時仍保留滾動條,以下是基于這思路的純 CSS 實作:
html {
overflow-y: scroll;
}
盡管“模態框抖動”問題解決了,但整體的外觀卻被一個無法使用的滾動條影響了,這無疑是設計中的硬傷, 在我們看來,更好的解決方案是完全地隱藏滾動條,純粹用 CSS 也是可以實作的,該方法(達到的效果)和 macOS 的表現并不是完全一致,(當用戶)滾動時滾動條仍然是不可見的,滾動條總是處于不可見狀態,然而頁面是可被滾動的,對于Chrome,Safari 和 Opera 而言,可以使用以下的 CSS:
.container::-webkit-scrollbar {
display: none;
}
IE 或 Edge 可用以下代碼:
.container {
-ms-overflow-style: none;
}
至于 Firefox,很不幸,沒有任何辦法隱藏滾動條, 正如你所見,并沒有任何銀彈,任何解決方案都有它的優點與缺點,應根據你專案的需要選擇最合適的,
外觀爭議
需要承認的是,滾動條的樣子在部分作業系統上并不好看,一些設計師喜歡完全掌控他們(所設計)應用的樣式,任何一絲細節也不放過,在
GitHub 上有上百個庫
借助 JavaScript 取代系統滾動條的默認實作,以達到自定義的效果,
但如果你想根據現有的瀏覽器定制一個滾動條呢?(很遺憾,)并沒有通用的 API,每個瀏覽器都有其獨特的代碼實作,
盡管5.5版本以后的 IE 瀏覽器允許你修改滾動條的樣式,但它只允許你修改滾動條的顏色,以下是如何重新繪制(滾動條)拖動部分與箭頭的代碼:
body {
scrollbar-face-color: blue;
}
但只改變顏色對提高用戶體驗而言幫助不大,據此,WebKit 的開發者在2009年提出了(修改滾動條)樣式的方案,以下是使用
-webkit
前綴在支持相關樣式的瀏覽器中模擬 macOS 滾動條樣式的代碼:
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
background-color: #c1c1c1;
border-radius: 4px;
}
Chrome、Safari、Opera 甚至于 UC 瀏覽器或者三星自帶的桌面瀏覽器都支持(上述 CSS),Edge
也有計劃實作它們
,但三年過去了,該計劃仍在中等優先級中(而尚未被實作), 當我們討論滾動條的定制時,Mozilla 基金會基本上是無視了設計師的需求,(有開發者在)
17年前
就已經提出了一個希望修改滾動條樣式的請求,而就在幾個月前,Jeff Griffiths(Firefox 瀏覽器總監)終于為這個問題作出了回答:
“除非團隊中有人對此有興趣,否則我對此毫不關心,”
公平地說,從 W3C 的角度看來,盡管 WebKit 的實作得到廣泛的支持,但它仍然不是標準,現有的為滾動條修改樣式的草案,是基于 IE 的:僅能修改它的顏色, 伴隨著請求如同 WebKit 一樣支持滾動條樣式修改
issue
的提交,爭議仍在繼續,如果你想影響 CSS 作業小組,是時候參與討論了,也許這不是優先級最高的問題,但(如同 WebKit 一樣修改滾動條樣式)得到標準化后,能使很多前端工程師與設計師輕松很多,
流暢的操作體驗
對于滾動而言,最常見的任務是登錄頁的導航(跳轉),通常,它是通過錨點鏈接來完成的,只需要知道元素的
id
即可:
<a href="#section">Section</a>
點擊該鏈接會
跳
到(該錨點對應的)區塊上,(然而) UX 設計師一般會堅持認為該程序應是平滑地運動的,
GitHub 上有大量造好的輪子
(幫你解決這個問題),然而它們或多或少都用到 JavaScript,(其實)只用一行代碼也能實作同樣的效果,最近DOM API 中的
Element.scrollIntoView()
可以通過
傳入配置物件
來實作平滑滾動:
elem.scrollIntoView({
behavior: 'smooth'
});
然而該屬性
兼容性較差
且仍是通過腳本(來控制樣式),如有可能,應盡量少用額外的腳本, 幸運的是,有一個全新的
CSS 屬性
(仍在作業草案中),可以用簡單的一行代碼改變整個頁面滾動的行為,
html {
scroll-behavior: smooth;
}
結果如下:

(從一個區塊跳到另一個)

(平滑地滾動) 你可以在
codepen
上試驗這個屬性,在撰寫本文時,
scroll-behavior
僅在 Chrome、 Firefox 與 Opera 上被支持,但我們希望它能被廣泛支持,因為使用 CSS (比使用 JavaScript)在解決頁面滾動問題時優雅得多,并更符合“
漸進增強
”的模式,
粘性 CSS
另一個常見的需求是根據滾動方向動態地定住元素,即有名的“粘性(即 CSS 中的
position: sticky
)”效應,

(一個粘性元素) 在以前的日子里,要實作一個“粘性”元素需要撰寫復雜的滾動處理函式去計算元素的大小,(然而)該函式較難處理元素在“黏住”與“不黏住”之間微小的延遲,(通常會)導致(元素)抖動的出現,通過 JavaScript 來實行(“粘性”元素)也有性能上的問題,特別是在(需要)呼叫 [
Element.getBoundingClientRect()
]時(
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect),%E3%80%82)
不久之前,CSS 實作了
position: sticky
屬性,只需通過指定(某方向上的)偏移量即可實作我們想要的效果,
.element {
position: sticky;
top: 50px;
}
(撰寫上述代碼后,)剩下的就交由瀏覽器實作即可,你可以在
codepen
上試驗一下,撰寫本文之時,
position: sticky
在各式瀏覽器(包括移動端瀏覽器)上支持良好,所以如果你還在使用 JavaScript 去解決這個問題的話,是時候換成純 CSS 的實作了,
全面使用函式節流
從瀏覽器的角度看來,滾動是一個
事件
,因此在 JavaScript 中是使用一個標準化的事件監聽器
addEventListener
去處理它: ,
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY;
/* doSomething with scrollTop */
});
用戶往往高頻率地滾動(頁面),但如果滾動事件觸發太頻繁的話,會導致性能上的問題,可以通過使用
函式節流
這一技巧去優化它,
window.addEventListener('scroll', throttle(() => {
const scrollTop = window.scrollY;
/* doSomething with scrollTop */
}));
你需要定義一個節流函式包裝原來的事件監聽函式,(節流函式是)減少被包裝函式的執行次數,只允許它在固定的時間間隔之內執行一次:
function throttle(action, wait = 1000) {
let time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
action();
time = Date.now();
}
}
}
為了使(節流后的)滾動更平滑,你可以通過使用
window.requestAnimationFrame()
來實作函式節流:
function throttle(action) {
let isRunning = false;
return function() {
if (isRunning) return;
isRunning = true;
window.requestAnimationFrame(() => {
action();
isRunning = false;
});
}
}
當然,你可以通過現有的開源輪子來
實作
,就像
Lodash
一樣,你可以訪問
codepen
來看看上述解決方案與 Lodash 中的
_.throttle
之間的區別, 使用哪個(開源庫)并不重要,重要的是在需要的時候,記得優化你(頁面中的)滾動處理函式,
在視窗中顯示
當你需要實作圖片
懶加載
或者
無限滾動
時,需要確定元素是否出現在視窗中,這可以在事件監聽器中處理,最常見的解決方案是使用
lement.getBoundingClientRect()
:
window.addEventListener('scroll', () => {
const rect = elem.getBoundingClientRect();
const inViewport = rect.bottom > 0 && rect.right > 0 &&
rect.left < window.innerWidth &&
rect.top < window.innerHeight;
});
上述代碼的問題在于每次呼叫
getBoundingClientRect
時都會觸發
回流
,嚴重地影響了性能,在事件處理函式中呼叫(
getBoundingClientRect
)尤為糟糕,就算使用了函式節流(的技巧)也可能對性能沒多大幫助, (回流是指瀏覽器為區域或整體地重繪某個元素,需要重新計算該元素在檔案中的位置與形狀,) 在2016年后,可以通過使用
Intersection Observer
這一 API 來解決問題,它允許你追蹤目標元素與其祖先元素或視窗的交叉狀態,此外,盡管只有
一部分
元素出現在視窗中,哪怕只有一像素,也可以選擇觸發回呼函式:
const observer = new IntersectionObserver(callback, options);
observer.observe(element);
(點擊
這里
,查看觸發回流的 DOM 屬性和方法,) 此 API 被廣泛地支持,但仍有一些瀏覽器需要
polyfill
,盡管如此,它仍是目前最好的解決方案,
滾動邊界問題
如果你的彈框或下拉串列是可滾動的,那你務必要了解
連鎖滾動
相關的問題:當用戶滾動到(彈框或下拉串列)末尾(后再繼續滾動時),整個頁面都會開始滾動, (連鎖滾動的表現) 當滾動元素到達底部時,你可以通過(改變)頁面的
overflow
屬性或在滾動元素的滾動事件處理函式中取消默認行為來解決這問題, 如果你選擇使用 JavaScript (來處理),請記住要處理的不是“scroll(事件)”,而是每當用戶使用滑鼠滾輪或觸摸板時觸發的
“wheel(事件)”
:
function handleOverscroll(event) {
const delta = -event.deltaY;
if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) {
elem.scrollTop = elem.scrollHeight;
event.preventDefault();
return false;
}
if (delta > elem.scrollTop) {
elem.scrollTop = 0;
event.preventDefault();
return false;
}
return true;
}
不幸的是,這個解決方案不太可靠,同時可能對(頁面)性能產生負面影響, 過度滾動對移動端的影響尤為嚴重,
Loren Brichter
在 iOS 的 Tweetie 應用上創造了一個“下拉重繪”的新手勢,這在 UX 社區中引起了轟動:包括 Twitter 與 Facebook 在內的各大應用紛紛采用了(相同的手勢), 當這個特性出現在安卓端的 Chrome 瀏覽器中時,問題出現了:它會重繪整個頁面而不是加載更多的內容,成為開發者在他們的應用中實作“下拉重繪”時的麻煩, CSS 通過
overscroll-behavior
這個新屬性解決問題,它通過控制元素滾動到盡頭時的行為來解決下拉重繪與連鎖滾動所帶來的問題,(它的屬性值中)也包含針對不同平臺特殊值:安卓的
glow
與 蘋果系統中的
rubber band
, 現在,上面 GIF 中的問題,在 Chrome、Opera 或 Firefox 中可以通過以下一行代碼來解決:
.element {
overscroll-behavior: contain;
}
公平地說,IE 與 Edge 實作了(它獨有的)
-ms-scroll-chaining
屬性
來控制連鎖滾動,但它并不能處理所有的情況,幸運的是,根據
這訊息
,微軟的瀏覽器已經準備實作
overscroll-behavior
這一屬性了,
觸屏之后
觸屏設備上的滾動(體驗)是一個很大的話題,深入討論需要另開一篇文章,然而,由于很多開發者忽略了這方面的內容,這里需要提及一下, (滾動手勢無處不在,令人沉迷,以至于想出了
如此瘋狂的主意
去解決“滾動上癮”的問題,) 周圍的人在智能手機螢屏上上下移動他們的手指的頻率是多少呢?經常這樣對吧,當你閱讀本文時,你很可能就在這么做,
當你的手指在螢屏上移動時,你期待的是:頁面內容平滑且流暢地移動,
蘋果公司
開創
了“慣性”滾動并擁有它的
專利
,它訊速地成為了用戶互動的標準并且我們對此已習以為常, 但你也許已經注意到了,盡管移動端系統會為你實作頁面上的慣性滾動,但當
頁面內某個元素
發生滾動時,即使用戶同樣期待慣性滾動,但它并不會出現,這令人沮喪, 這里有一個 CSS 的解決方案,但看起來更像是個 hack:
.element {
-webkit-overflow-scrolling: touch;
}
為什么這是個 hack 呢?首先,它只能在支持(webkit)前綴的瀏覽器上才能作業,其次,它只適用于觸屏設備,最后,如果瀏覽器不支持的話,你就這樣置之不理嗎?但無論如何,這總歸是一個解決方案,你可以試著使用它, 在觸屏設備上,另一個需要考慮的問題是開發者如何處理
touchstart
與
touchmove
事件觸發時可能存在的性能問題,它對用戶滾動體驗的影響非常大,
這里
詳細描述了整個問題,簡單來說,現代的瀏覽器雖然知道如何使得滾動變得平滑,但為確認(滾動)事件處理函式中是否執行了
Event.preventDefault()
以取消默認行為,有時仍可能需要花費500毫秒來等待事件處理函式執行完畢, 即使是一個空的事件監聽器,從不取消任何行為,鑒于瀏覽器仍會期待
preventDefault
的呼叫,也會對性能造成負面影響, 為了準確地告訴瀏覽器不必擔心(事件處理函式中)取消了默認行為,在
WHATWG
的 DOM 標準中存在著一個
不太顯眼的特性
(能解決這問題),(它就是)
Passive event listeners
,瀏覽器對它的支持還是
不錯的
,事件監聽函式新接受一個可選的物件作為引數,告訴瀏覽器當事件觸發時,事件處理函式永遠不會取消默認行為,(當然,添加此引數后,)在事件處理函式中呼叫
preventDefault
將不再產生效果,
element.addEventListener('touchstart', e => {
/* doSomething */
}, { passive: true });
針對不支持該引數的瀏覽器,這里也有一個
polyfill
,
這視頻
清晰地展示了此改進帶來的影響,
舊技術運行良好,為何還要改動?
在現代互聯網中,過渡地依賴 JavaScript 在各瀏覽器上實作相同的互動效果不再是合理的,“跨瀏覽器兼容性”已經成為過去式,更多的 CSS 屬性與 DOM API 方法正逐步被各大瀏覽器所支持, 在我們看來,當你的專案中,有特別酷炫的滾動效果時,
漸進增強
是最好的做法,
你應該提供(給用戶)所有(你能提供的)基礎用戶體驗,并逐步在更先進的瀏覽器上提供更好的體驗,
必要時使用 polyfill,它們不會產生(不必要的)依賴,一旦(某個 polyfill 所支持的屬性)得到廣泛地支持,你就可以輕松地將它刪掉, 六個月之前,在本文尚未成文之時,之前我們描述的屬性只被少量的瀏覽器所支持,而到了本文發表之時,這些屬性已被廣泛地支持, 也許到了現在,當你上下翻閱本文之時,(之前不支持某些屬性的)瀏覽器已經支持了該屬性,這使得你編程更容易,并使你的應用打包出來體積更小,
感謝閱讀至此!查閱瀏覽器的更新日志,積極參與討論,有助于 web 標準駛向正確的方向,祝大家一帆風順,順利滑(滾)向未來! 譯文:
https://www.zcfy.cc/article/scroll-to-the-future
原文鏈接:
evilmartians.com
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/254417.html
標籤:其他
上一篇:Qt撰寫地圖綜合應用27-點聚合
下一篇:【1】前端開發介紹
