摘要:用于獲得當前元素到定位父級頂部的距離偏移值。后來在項目中總會遇到滾動吸頂的效果需要實現,現在我將我知道的種滾動吸頂實現方式做詳細介紹。有兼容性問題,在微信瀏覽器某些版本中的值會為,于是乎也就有了第三種方案的兼容性寫法。
修改版預覽這篇文章是三天前寫就的,有大佬給我提了一些修改意見,我覺得這個意見確實中肯。所以就有了這個升級的修改版本。代碼同步更新到 GitHub 了。
修改內容如下:
添加了圖文說明,直觀的說明 getBoundingClientRect()集合含義
頻繁 reflow 風險該如何規避(優化滾動監聽)
監聽滾動帶來的性能問題(使用 IntersectionObserver, 新方案)
前言修改更新的內容在第 4 點和第 5 點,如果你看過本文,可以直接看修改更新的內容。或者你可以再看一遍。
我入職第二家公司接到的第一個需求就是修復之前外包做的滾動吸頂效果。我當時很納悶為何一個滾動吸頂會有 bug,后來我查看代碼才發現直接用的是 offsetTop 這個屬性,而且并沒有做兼容性處理。
offsetTop
用于獲得當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)。
定位父級 offsetParent 的定義是:與當前元素最近的 position != static 的父級元素。
或許寫這個代碼的人沒有注意到“定位父級”這個這個附屬條件。
后來在項目中總會遇到滾動吸頂的效果需要實現,現在我將我知道的 4 種滾動吸頂實現方式做詳細介紹。
目錄使用 position:sticky 實現
使用 JQuery 的 offset().top 實現
使用原生的 offsetTop 實現
使用 obj.getBoundingClientRect().top 實現
性能優化篇
以上這四種方式你都了解嗎?相關代碼已上傳到 GitHub ,感興趣的可以 clone 代碼到本地運行。望給個 star 支持一下。
四種實現方式我們先看一下效果圖:
一、使用 position:sticky 實現
1、粘性定位是什么?
粘性定位 sticky 相當于相對定位 relative 和固定定位 fixed 的結合;在頁面元素滾動過程中,某個元素距離其父元素的距離達到 sticky 粘性定位的要求時;元素的相對定位 relative 效果變成固定定位 fixed 的效果。
MDN 傳送門
2、如何使用?
使用條件:
父元素不能 overflow:hidden 或者 overflow:auto 屬性
必須指定 top、bottom、left、right 4 個值之一,否則只會處于相對定位
父元素的高度不能低于 sticky 元素的高度
sticky 元素僅在其父元素內生效
在需要滾動吸頂的元素加上以下樣式便可以實現這個效果:
.sticky { position: -webkit-sticky; position: sticky; top: 0; }
3、這個屬性好用嗎?
我們先看下在 Can I use 中看看這個屬性的兼容性:
可以看出這個屬性的兼容性并不是很好,因為這個 API 還只是實驗性的屬性。不過這個 API 在 IOS 系統的兼容性還是比較好的。
所以我們在生產環境如果使用這個 API 的時候一般會和下面的幾種方式結合使用。
二、使用 JQuery 的 offset().top 實現
我們知道 JQuery 中封裝了操作 DOM 和讀取 DOM 計算屬性的 API,基于 offset().top 這個 API 和 scrollTop() 的結合,我們也可以實現滾動吸頂效果。
...
window.addEventListener("scroll", self.handleScrollOne);
...
handleScrollOne: function() {
let self = this;
let scrollTop = $("html").scrollTop();
let offsetTop = $(".title_box").offset().top;
self.titleFixed = scrollTop > offsetTop;
}
...
這樣實現固然可以,不過由于 JQuery 慢慢的退出歷史的舞臺,我們在代碼中盡量不使用 JQuery 的 API。我們可以基于 offset().top 的源碼自己處理原生 offsetTop。于是乎就有了第三種方式。
scrolloTop() 有兼容性問題,在微信瀏覽器、IE、某些 firefox 版本中 $("html").scrollTop() 的值會為 0,于是乎也就有了第三種方案的兼容性寫法。
三、使用原生的 offsetTop 實現
我們知道 offsetTop 是相對定位父級的偏移量,倘若需要滾動吸頂的元素出現定位父級元素,那么 offsetTop 獲取的就不是元素距離頁面頂部的距離。
我們可以自己對 offsetTop 做以下處理:
getOffset: function(obj,direction){
let offsetL = 0;
let offsetT = 0;
while( obj!== window.document.body && obj !== null ){
offsetL += obj.offsetLeft;
offsetT += obj.offsetTop;
obj = obj.offsetParent;
}
if(direction === "left"){
return offsetL;
}else {
return offsetT;
}
}
使用:
...
window.addEventListener("scroll", self.handleScrollTwo);
...
handleScrollTwo: function() {
let self = this;
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
let offsetTop = self.getOffset(self.$refs.pride_tab_fixed);
self.titleFixed = scrollTop > offsetTop;
}
...
你是不是看出了以上兩種方式的一些問題?
我們一定需要使用 scrollTop - offsetTop 的值來實現滾動吸頂的效果嗎?答案是否定的。
我們一同看看第四種方案。
四、使用 obj.getBoundingClientRect().top 實現
定義:
這個 API 可以告訴你頁面中某個元素相對瀏覽器視窗上下左右的距離。
使用:
tab 吸頂可以使用 obj.getBoundingClientRect().top 代替 scrollTop - offsetTop,代碼如下:
// html
"pride_tab_fixed" ref="pride_tab_fixed">
"pride_tab" :class="titleFixed == true ");>
// some code
// vue
export default {
data(){
return{
titleFixed: false
}
},
activated(){
this.titleFixed = false;
window.addEventListener("scroll", this.handleScroll);
},
methods: {
//滾動監聽,頭部固定
handleScroll: function () {
let offsetTop = this.$refs.pride_tab_fixed.getBoundingClientRect().top;
this.titleFixed = offsetTop < 0;
// some code
}
}
}
1. getBoundingClientRect():
用于獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置。不包含文檔卷起來的部分。
該函數返回一個 object 對象,有8個屬性: top, right, buttom, left, width, height, x, y
2. offsetTop:
用于獲得當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)。
定位父級 offsetParent 的定義是:與當前元素最近的 position != static 的父級元素。
offsetTop 和 offsetParent 方法相結合可以獲得該元素到 body 上邊距的距離。代碼如下:
getOffset: function(obj,direction){
let offsetL = 0;
let offsetT = 0;
while( obj!== window.document.body && obj !== null ){
offsetL += obj.offsetLeft;
offsetT += obj.offsetTop;
obj = obj.offsetParent;
}
if(direction === "left"){
return offsetL;
}else {
return offsetT;
}
}
offsetWidth:
元素在水平方向上占用的空間大小:
offsetWidth = border-left + padding-left + width + padding-right + border-right
offsetHeight:
元素在垂直方向上占用的空間大小:
offsetHeight = border-top + padding-top + height + padding-bottom + border-bottom
注:如果存在垂直滾動條,offsetWidth 也包括垂直滾動條的寬度;如果存在水平滾動條,offsetHeight 也包括水平滾動條的高度;
offsetTop:
元素的上外邊框至 offsetParent 元素的上內邊框之間的像素距離;
offsetLeft:
元素的左外邊框至 offsetParent 元素的左內邊框之間的像素距離;
注意事項
所有偏移量屬性都是只讀的;
如果給元素設置了 display:none,則它的偏移量屬性都為 0;
每次訪問偏移量屬性都需要重新計算(保存變量);
在使用的時候可能出現 DOM 沒有初始化,就讀取了該屬性,這個時候會返回 0;對于這個問題我們需要等到 DOM 元素初始化完成后再執行。
出現抖動的原因是因為:在吸頂元素 position 變為 fixed 的時候,該元素就脫離了文檔流,下一個元素就進行了補位。就是這個補位操作造成了抖動。
解決方案
為這個吸頂元素添加一個等高的父元素,我們監聽這個父元素的 getBoundingClientRect().top 值來實現吸頂效果,即:
"title_box" ref="pride_tab_fixed">
"title" :class="titleFixed == true ");>
使用 `obj.getBoundingClientRect().top` 實現
這個方案就可以解決抖動的 Bug 了。
二、吸頂效果不能及時響應這個問題是我比較頭痛,之前我沒有在意過這個問題。直到有一天我用美團點外賣的時候,我才開始注意這個問題。
描述:
當頁面往下滾動時,吸頂元素需要等頁面滾動停止之后才會出現吸頂效果
當頁面往上滾動時,滾動到吸頂元素恢復文檔流位置時吸頂元素不恢復原樣,而等頁面停止滾動之后才會恢復原樣
原因: 在 ios 系統上不能實時監聽 scroll 滾動監聽事件,在滾動停止時才觸發其相關的事件。
解決方案:
還記得第一種方案中的 position:sticky 嗎?這個屬性在 IOS6 以上的系統中有良好的兼容性,所以我們可以區分 IOS 和 Android 設備做兩種處理。
IOS 使用 position:sticky,Android 使用滾動監聽 getBoundingClientRect().top 的值。
如果 IOS 版本過低呢?這里提供一種思路:window.requestAnimationFrame()。
性能優化篇(新增)
到此 4 中滾動吸頂的方式介紹完了,可是這樣就真的結束了嗎?其實還是有優化的空間的。
我們從兩個方向做性能優化(其實是一個方向):
避免過度的 reflow
優化滾動監聽事件
我們知道過度的 reflow 會使頁面的性能下降。所以我們需要盡可能的降低 reflow 的次數,給用戶更加流暢的感覺。
有的朋友或許會說這個我知道,可是這和滾動吸頂有什么關系呢?
不急,你是否還記得滾動吸頂使用了 offsetTop 或者 getBoundingClientRect().top 來獲取響應的偏移量呢?
既然有讀取元素的屬性就自然會導致頁面 reflow。
因此我們優化的方向就是從減少讀取元素屬性次數下手,查看代碼發現一觸發屏幕滾動事件就會調用相關方法讀取元素的偏移量。
優化方案解決這個問題有以下兩個方案:
犧牲平滑度滿足性能,使用節流控制相關方法的調用
使用 IntersectionObserver 和節流結合,也犧牲了平滑度。
這個方案很常見,不過其帶來的副作用也很明顯,就是在吸頂效果會有些延遲,如果產品可以接受那就不失為一種好方法。
這樣可以控制在一定時間內只讀取
這里節流函數就直接是用 lodash.js 封裝好的 throttle 方法。
代碼如下:
window.addEventListener("scroll", _.throttle(self.handleScrollThree, 50));
第二種方案相對來說容易接受一點,就是支持 IntersectionObserver 就用 IntersectionObserver,否則就用 throttle。
我們先講講 IntersectionObserver
IntersectionObserver 可以用來監聽元素是否進入了設備的可視區域之內,而不需要頻繁的計算來做這個判斷。
通過這個屬性我們就可以在元素不在可視范圍內,不去讀取元素的相對位置,已達到性能優化;當瀏覽器不支持這個屬性的時候就使用 throttle 來處理。
我們看看這個屬性的兼容性怎么樣:
大概支持了 60% 以上,在項目中還是可以使用的(你需要做好兼容性處理)。
關于 IntersectionObserver 如何使用,請看 MDN 或者 阮一峰教程。
使用 IntersectionObserver 和 throttle 優化的代碼如下:
IntersectionObserverFun: function() {
let self = this;
let ele = self.$refs.pride_tab_fixed;
if( !IntersectionObserver ){
let observer = new IntersectionObserver(function(){
let offsetTop = ele.getBoundingClientRect().top;
self.titleFixed = offsetTop < 0;
}, {
threshold: [1]
});
observer.observe(document.getElementsByClassName("title_box")[0]);
} else {
window.addEventListener("scroll", _.throttle(function(){
let offsetTop = ele.getBoundingClientRect().top;
self.titleFixed = offsetTop < 0;
}, 50));
}
},
注意
IntersectionObserver API 是異步的,不隨著目標元素的滾動同步觸發。
規格寫明,IntersectionObserver的實現,應該采用 requestIdleCallback()。它不會立即執行回調,它會調用 window.requestIdleCallback() 來異步的執行我們指定的回調函數,而且還規定了最大的延遲時間是 100 毫秒。
總結:
前端詞典系列這種 IntersectionObserver 和 throttle 結合的方案不失為一種可選擇的方案,這種方案的優點就在于可以有效地減少頁面 reflow 的風險,不過缺點也是有的,需要犧牲頁面的平滑度。具體該如何取舍,就看業務的需要啦。
《前端詞典》這個系列會持續更新,每一期我都會講一個出現頻率較高的知識點。希望大家在閱讀的過程當中可以斧正文中出現不嚴謹或是錯誤的地方,本人將不勝感激;若通過本系列而有所得,本人亦將不勝欣喜。
如果你覺得我的文章寫的還不錯,可以關注我的微信公眾號,公眾號里會提前劇透呦。
你也可以添加我的微信 wqhhsd, 歡迎交流。
熱門文章傳送門【前端詞典】滾動穿透問題的解決方案
繼承(一) - 原型鏈你真的懂嗎?
【前端詞典】進階必備的網絡基礎(上)
【前端詞典】進階必備的網絡基礎(下)
【前端詞典】實現 Canvas 下雪背景引發的性能思考
【前端詞典】從輸入 URL 到展現涉及哪些緩存環節(非常詳細)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7347.html
摘要:第一篇文章我會結合和的部分源碼,來說明注入生命周期的過程。說到源碼,其實沒有想象的那么難。但是源碼的調用樹會復雜很多。應用的業務代碼逐漸復雜,事件事件總線等通信的方式的弊端就會愈發明顯。狀態管理是組件解耦的重要手段。前言 這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會圍繞著 Vue 展開,希望這 9 篇文章可以使大家加深對 Vue 的了解。當然這些文章的前提是默認你對 ...
摘要:使用歸類重復樣式和重復變量一樣,重復的樣式也可以歸類。解決方案可以使用如下的寫法兩端對齊姓名手機號碼賬號密碼效果如下相關文章輸出計劃最近總有朋友問我相關的問題,因此接下來我會輸出篇相關的文章,希望對大家有一定的幫助。前言 在這篇文章我會介紹 9 個使你的 CSS 更加簡潔優雅的使用技巧。這些技巧小生經常使用,覺得挺高效實用,所以也就有了這篇文章。 9 個 CSS 技巧 特此聲明,這里說的...
摘要:使用歸類重復樣式和重復變量一樣,重復的樣式也可以歸類。解決方案可以使用如下的寫法兩端對齊姓名手機號碼賬號密碼效果如下相關文章輸出計劃最近總有朋友問我相關的問題,因此接下來我會輸出篇相關的文章,希望對大家有一定的幫助。前言 在這篇文章我會介紹 9 個使你的 CSS 更加簡潔優雅的使用技巧。這些技巧小生經常使用,覺得挺高效實用,所以也就有了這篇文章。 9 個 CSS 技巧 特此聲明,這里說的...
摘要:使用上面的截圖是微信網頁版的消息提示。代碼很簡單微信網頁版微信網頁版微信全屏定義這個可以使你所打開的頁面全屏展示,沒有其他頁面外的內容展示在屏幕上。如果你想加群交流,你也可以添加我的微信。前言 在日常開發中總是和各種 API 打交道,我們名為前端工程師實為 API 調用工程師。這篇文章我就分享 8 個有趣的 API,你若通過閱讀這篇文章對前端增加一點點的樂趣,對我來說也是一種鼓勵。 這幾...
摘要:接下就說下我對滾動穿透問題解決方案探索的過程,希望對大家有點啟發。心想來了突然意識到寫彈窗的時候忘記處理滾動穿透的問題了。下期預告前端詞典繼承必懂知識點傳送門前端詞典代理的概念及其應用前端詞典滾動穿透問題的解決方案 背景 產品有三寶,彈窗,浮層加引導; 設計有三寶,透明,陰影加圓角; 運營有三寶,短信,推送加紅包; 程序員有一寶,這個做不了。 隨著移動端市場的份額越大,需求就越多...
閱讀 3807·2023-04-25 19:07
閱讀 3553·2021-11-22 12:02
閱讀 3138·2021-10-12 10:11
閱讀 3907·2021-09-03 10:49
閱讀 2891·2019-08-30 13:21
閱讀 2997·2019-08-30 11:14
閱讀 2088·2019-08-29 15:40
閱讀 2872·2019-08-28 18:29