前言
我入職第二家公司接到的第一個需求就是修復之前外包做的滾動吸頂效果。我當時很納悶為何一個滾動吸頂會有 bug裤翩,后來我查看代碼才發(fā)現(xiàn)直接用的是?offsetTop?這個屬性,而且并沒有做兼容性處理。
offsetTop
用于獲得當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)施敢。
定位父級?offsetParent?的定義是:與當前元素最近的 position != static 的父級元素曹傀。
或許寫這個代碼的人沒有注意到“定位父級”這個這個附屬條件辐脖。
后來在項目中總會遇到滾動吸頂?shù)男Ч枰獙崿F(xiàn),現(xiàn)在我將我知道的 4 種滾動吸頂實現(xiàn)方式做詳細介紹皆愉。
目錄
href="https://zhuanlan.zhihu.com/p/62125575/edit#a01">使用 position:sticky 實現(xiàn)
使用 JQuery 的 offset().top 實現(xiàn)
f="https://zhuanlan.zhihu.com/p/62125575/edit#a03">使用原生的 offsetTop 實現(xiàn)
href="https://zhuanlan.zhihu.com/p/62125575/edit#a04">使用 obj.getBoundingClientRect().top 實現(xiàn)
以上這四種方式你都了解嗎嗜价?相關代碼已上傳到?GitHub?,感興趣的可以 clone 代碼到本地運行幕庐。望給個 star 支持一下久锥。
四種實現(xiàn)方式
我們先看一下效果圖:
一、使用?position:sticky?實現(xiàn)
1异剥、粘性定位是什么瑟由?
粘性定位?sticky?相當于相對定位?relative?和固定定位?fixed?的結(jié)合;在頁面元素滾動過程中冤寿,某個元素距離其父元素的距離達到?sticky?粘性定位的要求時歹苦;元素的相對定位?relative效果變成固定定位?fixed?的效果青伤。
2、如何使用暂氯?
使用條件:
父元素不能?overflow:hidden?或者?overflow:auto?屬性
必須指定?top潮模、bottom、left痴施、right?4 個值之一擎厢,否則只會處于相對定位
父元素的高度不能低于?sticky?元素的高度
sticky?元素僅在其父元素內(nèi)生效
在需要滾動吸頂?shù)脑丶由弦韵聵邮奖憧梢詫崿F(xiàn)這個效果:
.sticky { position: -webkit-sticky; position: sticky; top: 0; }
3、這個屬性好用嗎辣吃?
我們先看下在 Can I use 中看看這個屬性的兼容性:
可以看出這個屬性的兼容性并不是很好动遭,因為這個 API 還只是實驗性的屬性。不過這個 API 在 IOS 系統(tǒng)的兼容性還是比較好的神得。
所以我們在生產(chǎn)環(huán)境如果使用這個 API 的時候一般會和下面的幾種方式結(jié)合使用厘惦。
二、使用 JQuery 的?offset().top?實現(xiàn)
我們知道 JQuery 中封裝了操作 DOM 和讀取 DOM 計算屬性的 API哩簿,基于?offset().top?這個 API 和?scrollTop()?的結(jié)合宵蕉,我們也可以實現(xiàn)滾動吸頂效果。
...
window.addEventListener('scroll', self.handleScrollOne);
...
handleScrollOne: function() {
let self = this;
let scrollTop = $('html').scrollTop();
let offsetTop = $('.title_box').offset().top;
self.titleFixed = scrollTop > offsetTop;
}
...
這樣實現(xiàn)固然可以节榜,不過由于 JQuery 慢慢的退出歷史的舞臺羡玛,我們在代碼中盡量不使用 JQuery 的 API。我們可以基于offset().top的源碼自己處理原生offsetTop宗苍。于是乎就有了第三種方式稼稿。
scrolloTop() 有兼容性問題,在微信瀏覽器讳窟、IE让歼、某些 firefox 版本中 $('html').scrollTop() 的值會為 0,于是乎也就有了第三種方案的兼容性寫法丽啡。
三谋右、使用原生的?offsetTop?實現(xiàn)
我們知道?offsetTop?是相對定位父級的偏移量,倘若需要滾動吸頂?shù)脑爻霈F(xiàn)定位父級元素碌上,那么?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的值來實現(xiàn)滾動吸頂?shù)男Ч麊崃笥瑁看鸢甘欠穸ǖ摹?/p>
我們一同看看第四種方案天梧。
四、使用?obj.getBoundingClientRect().top?實現(xiàn)
定義:
這個?API?可以告訴你頁面中某個元素相對瀏覽器視窗上下左右的距離霞丧。
使用:
tab 吸頂可以使用?obj.getBoundingClientRect().top?代替?scrollTop - offsetTop,代碼如下:
// html
<div class="pride_tab_fixed" ref="pride_tab_fixed">
<div class="pride_tab" :class="titleFixed == true ? 'isFixed' :''">
// some code
</div>
</div>
// vue
export default {
data(){
return{
titleFixed: false
}
},
activated(){
this.titleFixed = false;
window.addEventListener('scroll', this.handleScroll);
},
methods: {
//滾動監(jiān)聽呢岗,頭部固定
handleScroll: function () {
let offsetTop = this.$refs.pride_tab_fixed.getBoundingClientRect().top;
this.titleFixed = offsetTop < 0;
// some code
}
}
}
offsetTop 和 getBoundingClientRect() 區(qū)別
1. getBoundingClientRect():
用于獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置后豫。不包含文檔卷起來的部分悉尾。
該函數(shù)返回一個?object?對象,有6個屬性:?top, right, buttom, left, width, height挫酿。?(在 IE 中构眯,默認坐標從(2,2)開始計算,只返回?top,lef,right,bottom?四個值)
2. offsetTop:
用于獲得當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)早龟。
定位父級offsetParent的定義是:與當前元素最近的 position != static 的父級元素惫霸。
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:
元素在水平方向上占用的空間大写械堋:
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 元素的上內(nèi)邊框之間的像素距離藏杖;
offsetLeft:
元素的左外邊框至 offsetParent 元素的左內(nèi)邊框之間的像素距離将塑;
注意事項
所有偏移量屬性都是只讀的;
如果給元素設置了 display:none蝌麸,則它的偏移量屬性都為 0抬旺;
每次訪問偏移量屬性都需要重新計算(保存變量);
在使用的時候可能出現(xiàn) DOM 沒有初始化祥楣,就讀取了該屬性,這個時候會返回 0汉柒;對于這個問題我們需要等到 DOM 元素初始化完成后再執(zhí)行误褪。
遇到的兩個問題
一、吸頂?shù)哪且豢贪殡S抖動
出現(xiàn)抖動的原因是因為:在吸頂元素 position 變?yōu)?fixed 的時候碾褂,該元素就脫離了文檔流兽间,下一個元素就進行了補位。就是這個補位操作造成了抖動正塌。
解決方案
為這個吸頂元素添加一個等高的父元素嘀略,我們監(jiān)聽這個父元素的?getBoundingClientRect().top值來實現(xiàn)吸頂效果,即:
<div class="title_box" ref="pride_tab_fixed">
<div class="title" :class="titleFixed == true ? 'isFixed' :''">
使用 `obj.getBoundingClientRect().top` 實現(xiàn)
</div>
</div>
這個方案就可以解決抖動的 Bug 了乓诽。
二帜羊、吸頂效果不能及時響應
這個問題是我比較頭痛,之前我沒有在意過這個問題鸠天。直到有一天我用美團點外賣的時候讼育,我才開始注意這個問題。
描述:
當頁面往下滾動時,吸頂元素需要等頁面滾動停止之后才會出現(xiàn)吸頂效果
當頁面往上滾動時奶段,滾動到吸頂元素恢復文檔流位置時吸頂元素不恢復原樣饥瓷,而等頁面停止?jié)L動之后才會恢復原樣
原因:
在 ios 系統(tǒng)上不能實時監(jiān)聽 scroll 滾動監(jiān)聽事件,在滾動停止時才觸發(fā)其相關的事件痹籍。
解決方案:
還記得第一種方案中的 position:sticky 嗎呢铆?這個屬性在 IOS6 以上的系統(tǒng)中有良好的兼容性,所以我們可以區(qū)分 IOS 和 Android 設備做兩種處理蹲缠。
IOS 使用position:sticky棺克,Android 使用滾動監(jiān)聽getBoundingClientRect().top的值。
如果 IOS 版本過低呢吼砂?這里提供一種思路:window.requestAnimationFrame()逆航。
前端詞典系列
《前端詞典》這個系列會持續(xù)更新,每一期我都會講一個出現(xiàn)頻率較高的知識點渔肩。希望大家在閱讀的過程當中可以斧正文中出現(xiàn)不嚴謹或是錯誤的地方因俐,本人將不勝感激;若通過本系列而有所得周偎,本人亦將不勝欣喜抹剩。
內(nèi)容:?前端以及網(wǎng)絡相關知識點的介紹并加以實際應用作為輔助。
目的:?這個系列的文章可以對讀者起到一點幫助蓉坎,解開一些迷惑澳眷。
希望各位多指點一二,不吝賜教蛉艾。
如果你覺得我的文章寫的還不錯钳踊,可以關注我或者加我的專欄討論。