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