【前端詞典】4 種滾動吸頂實現(xiàn)方式的比較

前言

我入職第二家公司接到的第一個需求就是修復之前外包做的滾動吸頂效果。我當時很納悶為何一個滾動吸頂會有 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?的效果青伤。

MDN 傳送門

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)絡相關知識點的介紹并加以實際應用作為輔助。

目的:?這個系列的文章可以對讀者起到一點幫助蓉坎,解開一些迷惑澳眷。

希望各位多指點一二,不吝賜教蛉艾。

如果你覺得我的文章寫的還不錯钳踊,可以關注我或者加我的專欄討論。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勿侯,一起剝皮案震驚了整個濱河市拓瞪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌助琐,老刑警劉巖祭埂,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兵钮,居然都是意外死亡蛆橡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門掘譬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泰演,“玉大人,你說我怎么就攤上這事葱轩≈嘌” “怎么了柏锄?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長复亏。 經(jīng)常有香客問我趾娃,道長,這世上最難降的妖魔是什么缔御? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任抬闷,我火速辦了婚禮,結(jié)果婚禮上耕突,老公的妹妹穿的比我還像新娘笤成。我一直安慰自己,他們只是感情好眷茁,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布炕泳。 她就那樣靜靜地躺著,像睡著了一般上祈。 火紅的嫁衣襯著肌膚如雪培遵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天登刺,我揣著相機與錄音籽腕,去河邊找鬼。 笑死纸俭,一個胖子當著我的面吹牛皇耗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍很,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼郎楼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窒悔?” 一聲冷哼從身側(cè)響起箭启,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛉迹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體放妈,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡北救,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芜抒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珍策。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宅倒,靈堂內(nèi)的尸體忽然破棺而出攘宙,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布蹭劈,位于F島的核電站疗绣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铺韧。R本人自食惡果不足惜多矮,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哈打。 院中可真熱鬧塔逃,春花似錦、人聲如沸料仗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽立轧。三九已至格粪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肺孵,已是汗流浹背匀借。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留平窘,地道東北人吓肋。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像瑰艘,于是被迫代替她去往敵國和親是鬼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容