滾動吸頂效果--四種方式實現

滾動吸頂效果。我當時很納悶爲什麼一個滾動吸頂會有 bug,後來我查看代碼才發現直接用的是 offsetTop 這個屬性,並且並無作兼容性處理。
html

offsetTopvue

用於得到當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)。ios

定位父級 offsetParent 的定義是:與當前元素最近的 position != static 的父級元素。web

或許寫這個代碼的人沒有注意到「定位父級」這個這個附屬條件。
後來在項目中總會遇到滾動吸頂的效果須要實現,如今我將我知道的 4 種滾動吸頂實現方式作詳細介紹。瀏覽器

目錄微信

  1. 使用 position:sticky 實現
  2. 使用 JQuery 的 offset().top 實現
  3. 使用原生的 offsetTop 實現
  4. 使用 obj.getBoundingClientRect().top 實現

望給個 star 支持一下。app

四種實現方式ide

咱們先看一下效果圖:函數


1、使用 position:sticky 實現ui

一、粘性定位是什麼?

粘性定位 sticky 至關於相對定位 relative 和固定定位 fixed 的結合;在頁面元素滾動過程當中,某個元素距離其父元素的距離達到 sticky 粘性定位的要求時;元素的相對定位 relative 效果變成固定定位 fixed 的效果。

MDN 傳送門

二、如何使用?

使用條件:

父元素不能 overflow:hidden 或者 overflow:auto 屬性
必須指定 top、bottom、left、right 4 個值之一,不然只會處於相對定位
父元素的高度不能低於 sticky 元素的高度
sticky 元素僅在其父元素內生效

在須要滾動吸頂的元素加上如下樣式即可以實現這個效果:

?
1
2
3
4
5
.sticky {
position : -webkit-sticky;
position : sticky;
top : 0 ;
}

三、這個屬性好用嗎?

咱們先看下在 Can I use 中看看這個屬性的兼容性:

能夠看出這個屬性的兼容性並非很好,由於這個 API 還只是實驗性的屬性。不過這個 API 在 IOS 系統的兼容性仍是比較好的。
因此咱們在生產環境若是使用這個 API 的時候通常會和下面的幾種方式結合使用。

2、使用 JQuery 的 offset().top 實現

咱們知道 JQuery 中封裝了操做 DOM 和讀取 DOM 計算屬性的 API,基於 offset().top 這個 API 和 scrollTop() 的結合,咱們也能夠實現滾動吸頂效果。

?
1
2
3
4
5
6
7
8
9
10
...
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,因而乎也就有了第三種方案的兼容性寫法。

3、使用原生的 offsetTop 實現

咱們知道 offsetTop 是相對定位父級的偏移量,假若須要滾動吸頂的元素出現定位父級元素,那麼 offsetTop 獲取的就不是元素距離頁面頂部的距離。

咱們能夠本身對 offsetTop 作如下處理:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

使用:

?
1
2
3
4
5
6
7
8
9
10
...
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 的值來實現滾動吸頂的效果嗎?答案是否認的。

咱們一同看看第四種方案。

4、使用 obj.getBoundingClientRect().top 實現

定義:
這個 API 能夠告訴你頁面中某個元素相對瀏覽器視窗上下左右的距離。
使用:
tab 吸頂可使用 obj.getBoundingClientRect().top 代替 scrollTop - offsetTop,代碼以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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: {
//滾動監聽,頭部固定
handleScroll: function () {
let offsetTop = this .$refs.pride_tab_fixed.getBoundingClientRect().top;
this .titleFixed = offsetTop < 0;
// some code
}
}
}

offsetTop 和 getBoundingClientRect() 區別

1. getBoundingClientRect():

用於得到頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置。不包含文檔卷起來的部分。
該函數返回一個 object 對象,有6個屬性:
top, right, buttom, left, width, height。
(在 IE 中,默認座標從(2,2)開始計算,只返回 top,lef,right,bottom 四個值)

2. offsetTop:

用於得到當前元素到定位父級( element.offsetParent )頂部的距離(偏移值)。

定位父級 offsetParent 的定義是:與當前元素最近的 position != static 的父級元素。

offsetTop 和 offsetParent 方法相結合能夠得到該元素到 body 上邊距的距離。代碼以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 元素的左內邊框之間的像素距離;

注意事項

  1. 全部偏移量屬性都是隻讀的;
  2. 若是給元素設置了 display:none,則它的偏移量屬性都爲 0;
  3. 每次訪問偏移量屬性都須要從新計算(保存變量);
  4. 在使用的時候可能出現 DOM 沒有初始化,就讀取了該屬性,這個時候會返回 0;對於這個問題咱們須要等到 DOM 元素初始化完成後再執行。

遇到的兩個問題

1、吸頂的那一刻伴隨抖動

出現抖動的緣由是由於:在吸頂元素 position 變爲 fixed 的時候,該元素就脫離了文檔流,下一個元素就進行了補位。就是這個補位操做形成了抖動。

解決方案
爲這個吸頂元素添加一個等高的父元素,咱們監聽這個父元素的 getBoundingClientRect().top 值來實現吸頂效果,即:

?
1
2
3
4
5
<div class= "title_box" ref= "pride_tab_fixed" >
<div class= "title" :class= "titleFixed == true ? 'isFixed' :''" >
使用 `obj.getBoundingClientRect().top` 實現
</div>
</div>

這個方案就能夠解決抖動的 Bug 了。

2、吸頂效果不能及時響應

這個問題是我比較頭痛,以前我沒有在乎過這個問題。直到有一天我用美團點外賣的時候,我纔開始注意這個問題。
描述:

當頁面往下滾動時,吸頂元素須要等頁面滾動中止以後纔會出現吸頂效果
當頁面往上滾動時,滾動到吸頂元素恢復文檔流位置時吸頂元素不恢復原樣,而等頁面中止滾動以後纔會恢復原樣

緣由:

在 ios 系統上不能實時監聽 scroll 滾動監聽事件,在滾動中止時才觸發其相關的事件。
解決方案:
還記得第一種方案中的 position:sticky 嗎?這個屬性在 IOS6 以上的系統中有良好的兼容性,因此咱們能夠區分 IOS 和 Android 設備作兩種處理。

IOS 使用 position:sticky,Android 使用滾動監聽 getBoundingClientRect().top 的值。

若是 IOS 版本太低呢?這裏提供一種思路:window.requestAnimationFrame()。

相關文章
相關標籤/搜索