亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > CSS > 正文

css實現5種滾動吸頂實現方式的比較(性能升級版)

2024-07-11 09:00:10
字體:
來源:轉載
供稿:網友

修改版預覽

這篇文章是三天前寫就的,有大佬給我提了一些修改意見,我覺得這個意見確實中肯。所以就有了這個升級的修改版本。代碼同步更新到 GitHub 了。

修改內容如下:

  1. 添加了圖文說明,直觀的說明 getBoundingClientRect()集合含義
  2. 頻繁 reflow 風險該如何規避(優化滾動監聽)
  3. 監聽滾動帶來的性能問題(使用 IntersectionObserver, 新方案)

修改更新的內容在第 4 點和第 5 點,如果你看過本文,可以直接看修改更新的內容?;蛘吣憧梢栽倏匆槐?。

前言

我入職第二家公司接到的第一個需求就是修復之前外包做的滾動吸頂效果。我當時很納悶為何一個滾動吸頂會有 bug,后來我查看代碼才發現直接用的是 offsetTop 這個屬性,而且并沒有做兼容性處理。

offsetTop

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

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

或許寫這個代碼的人沒有注意到“定位父級”這個這個附屬條件。

后來在項目中總會遇到滾動吸頂的效果需要實現,現在我將我知道的 4 種滾動吸頂實現方式做詳細介紹。

以上這四種方式你都了解嗎?相關代碼已上傳到 GitHub ,感興趣的可以 clone 代碼到本地運行。望給個 star 支持一下。

四種實現方式

我們先看一下效果圖:

一、使用 position:sticky 實現

1、粘性定位是什么?

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

MDN 傳送門

2、如何使用?

使用條件:

  1. 父元素不能 overflow:hidden 或者 overflow:auto 屬性
  2. 必須指定 top、bottom、left、right 4 個值之一,否則只會處于相對定位
  3. 父元素的高度不能低于 sticky 元素的高度
  4. 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<div class="pride_tab_fixed" ref="pride_tab_fixed">    <div class="pride_tab" :class="titleFixed == true ? 'isFixed' :''">        // some code    </div></div> // vueexport 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 對象,有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:

元素在水平方向上占用的空間大?。?/p>

offsetWidth =  border-left + padding-left + width + padding-right + border-right

offsetHeight:

元素在垂直方向上占用的空間大?。?/p>

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 元素初始化完成后再執行。

遇到的兩個問題

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

出現抖動的原因是因為:在吸頂元素 position 變為 fixed 的時候,該元素就脫離了文檔流,下一個元素就進行了補位。就是這個補位操作造成了抖動。

解決方案

為這個吸頂元素添加一個等高的父元素,我們監聽這個父元素的 getBoundingClientRect().top 值來實現吸頂效果,即:

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

這個方案就可以解決抖動的 Bug 了。

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

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

描述:

  1. 當頁面往下滾動時,吸頂元素需要等頁面滾動停止之后才會出現吸頂效果
  2. 當頁面往上滾動時,滾動到吸頂元素恢復文檔流位置時吸頂元素不恢復原樣,而等頁面停止滾動之后才會恢復原樣

原因:

在 ios 系統上不能實時監聽 scroll 滾動監聽事件,在滾動停止時才觸發其相關的事件。

解決方案:

還記得第一種方案中的 position:sticky 嗎?這個屬性在 IOS6 以上的系統中有良好的兼容性,所以我們可以區分 IOS 和 Android 設備做兩種處理。

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

如果 IOS 版本過低呢?這里提供一種思路:window.requestAnimationFrame()。

性能優化篇(新增)

到此 4 中滾動吸頂的方式介紹完了,可是這樣就真的結束了嗎?其實還是有優化的空間的。
我們從兩個方向做性能優化(其實是一個方向):

  1. 避免過度的 reflow
  2. 優化滾動監聽事件

問題定位過程

我們知道過度的 reflow 會使頁面的性能下降。所以我們需要盡可能的降低 reflow 的次數,給用戶更加流暢的感覺。

有的朋友或許會說這個我知道,可是這和滾動吸頂有什么關系呢?

不急,你是否還記得滾動吸頂使用了 offsetTop 或者 getBoundingClientRect().top 來獲取響應的偏移量呢?

既然有讀取元素的屬性就自然會導致頁面 reflow。

因此我們優化的方向就是從減少讀取元素屬性次數下手,查看代碼發現一觸發屏幕滾動事件就會調用相關方法讀取元素的偏移量。

優化方案

解決這個問題有以下兩個方案:

  1. 犧牲平滑度滿足性能,使用節流控制相關方法的調用
  2. 使用 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 的風險,不過缺點也是有的,需要犧牲頁面的平滑度。具體該如何取舍,就看業務的需要啦。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久久免费观看| 国产欧美日韩免费看aⅴ视频| 欧美电影院免费观看| 日韩极品精品视频免费观看| 欧美日韩在线一区| 国产精品第一页在线| 亚洲成av人乱码色午夜| 色狠狠久久aa北条麻妃| 亚洲福利视频免费观看| 性视频1819p久久| 亚洲经典中文字幕| 日韩中文av在线| 97久久超碰福利国产精品…| 97在线免费观看视频| 国产区精品在线观看| 亚洲欧洲在线看| 亚洲精品视频在线播放| 国产精品日本精品| 欧美精品videosex极品1| 欧美日韩另类在线| 91禁外国网站| 中文字幕精品在线视频| 欧美尺度大的性做爰视频| 亚洲精品一二区| 亚洲欧洲黄色网| 在线亚洲午夜片av大片| 国产精品v日韩精品| 国产精品久久久久久久7电影| 国产亚洲欧美日韩美女| 国产成人高清激情视频在线观看| 亚洲精品中文字幕有码专区| 一区二区三区国产在线观看| 法国裸体一区二区| 青青草原一区二区| 成人精品久久av网站| 在线精品播放av| 国产精品aaa| 久久欧美在线电影| 亚洲免费成人av电影| 国产视频精品自拍| 久久精品国产清自在天天线| 欧美一级大片在线观看| 中文字幕日韩精品有码视频| 国产成人精品久久| 亚洲深夜福利网站| 亚洲女同性videos| 欧美亚洲视频一区二区| 美女福利精品视频| 国产成人a亚洲精品| 欧美一级黑人aaaaaaa做受| 欧美一区二区三区艳史| 亚洲性日韩精品一区二区| 国产一区二区三区网站| 国产suv精品一区二区| 国产亚洲精品va在线观看| 中文字幕亚洲综合久久筱田步美| 亚洲国产成人久久综合| 黑人精品xxx一区一二区| 8090成年在线看片午夜| 国产精品亚洲自拍| 亚洲精品国产suv| 久久久亚洲国产天美传媒修理工| 欧美日韩中文字幕日韩欧美| 国产欧美最新羞羞视频在线观看| 日韩av影片在线观看| 国产精品xxxxx| 国产精品一香蕉国产线看观看| 午夜精品久久久久久久男人的天堂| 日韩一二三在线视频播| 欧美激情第1页| 日韩欧美在线播放| 国产成人鲁鲁免费视频a| 亚洲一区二区日本| 中文字幕自拍vr一区二区三区| 国产精品88a∨| 日韩欧美福利视频| 亚洲国产精品久久久久秋霞不卡| 欧美一区二粉嫩精品国产一线天| 欧美激情一区二区久久久| 久久久999国产| 色阁综合伊人av| 亚洲成人激情在线观看| 久久久人成影片一区二区三区观看| 久久精品这里热有精品| 欧美精品激情视频| 日本不卡视频在线播放| 久久久久国产精品免费网站| 97在线精品视频| 久久久视频免费观看| 97超碰色婷婷| 这里只有精品视频在线| 亚洲激情第一页| 51色欧美片视频在线观看| 欧美成aaa人片在线观看蜜臀| 亚洲第一色在线| 日韩精品在线观看视频| 精品在线观看国产| 久久天天躁狠狠躁夜夜爽蜜月| 久久久免费电影| 91国内免费在线视频| 精品香蕉在线观看视频一| 国产综合久久久久久| 欧美成人免费播放| 国产丝袜一区视频在线观看| 日韩一级黄色av| 青青久久av北条麻妃黑人| 亚洲伊人久久综合| 亚洲欧美激情一区| 日韩在线中文视频| 日韩高清中文字幕| 日本亚洲欧洲色| 日韩专区中文字幕| 2025国产精品视频| 精品成人av一区| xxx欧美精品| 国产精自产拍久久久久久蜜| 亚洲人成电影网站色www| 精品国产一区二区三区久久狼黑人| 精品人伦一区二区三区蜜桃网站| 欧美精品久久久久久久免费观看| 国产精品27p| 欧美孕妇与黑人孕交| 久久久精品视频在线观看| 伊人久久大香线蕉av一区二区| 国产日韩欧美成人| 欧美日韩视频免费播放| 国产成人福利网站| 亚洲香蕉成人av网站在线观看| 欧美www在线| 欧美性xxxx| 国产成人av在线播放| 色琪琪综合男人的天堂aⅴ视频| 欧美最猛黑人xxxx黑人猛叫黄| 国产成人aa精品一区在线播放| 久久久精品在线观看| 日韩精品一区二区视频| 成人福利视频网| 成人免费大片黄在线播放| 性色av香蕉一区二区| 国产成人精品综合| 日韩高清人体午夜| 国产成人精品最新| 在线成人免费网站| 欧美片一区二区三区| 亚洲精品欧美日韩专区| 日韩美女视频中文字幕| 欧美大片在线影院| 色先锋资源久久综合5566| 久久精品亚洲国产| 国产精品吹潮在线观看| 亚洲午夜av电影| 欧美性在线观看| 一区二区三欧美| 8050国产精品久久久久久| 日韩欧美视频一区二区三区| 亚洲欧美另类在线观看| 欧美激情视频在线免费观看 欧美视频免费一| 在线观看日韩专区| 国产精品亚洲综合天堂夜夜| 国产偷国产偷亚洲清高网站| 国产日韩欧美电影在线观看| 国产精品男人的天堂| 成人免费在线网址| 成人有码在线视频|