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

首頁 > 編程 > JavaScript > 正文

使用getBoundingClientRect方法實現簡潔的sticky組件的方法

2019-11-20 10:21:47
字體:
來源:轉載
供稿:網友

sticky組件,通常應用于導航條或者工具欄,當網頁在某一區域滾動的時候,將導航條或工具欄這類元素固定在頁面頂部或底部,方便用戶快速進行這類元素提供的操作。本文介紹這種組件的實現思路,并提供一個同時支持將sticky元素固定在頂部或底部的具體實現,由于這種組件在網站中非常常見,所以有必要掌握它的實現方式,以便在有需要的時候基于它的思路寫出功能更多的組件出來。

固定在頂部的demo效果(對應sticky-top.html):

固定在底部的demo效果(對應sticky-bottom.html):

1. 實現思路

實現這個組件的關鍵在于找到元素何時被固定以及何時被取消固定的臨界點,要找到這個臨界點,首先要詳細看看前面demo的變化過程。在前面的demo中,有一個導航條元素,也就是我們要控制固定與否的元素,我把它稱為sticky元素;還有一個元素,它用來顯示網頁的一塊列表內容,這個列表元素跟sticky元素在功能上是相關的,因為sticky元素要導航的正是這個列表元素提供的內容,本文在開始介紹sticky組件的功能時,就說過sticky組件固定是發生在網頁滾動至某一區域的時候,離開這一區域就會取消固定,這個滾動區域或者說滾動范圍,就是由列表元素來決定的,所以這個列表元素是找到臨界點的關鍵,它表示sticky組件可被固定的網頁滾動范圍,為了后面引用方便,我把這個元素稱為target元素。下面就來詳細了解下前面demo的變化過程,由于固定在底部的情況與固定在頂部的情況實現思路是相通的,如果弄明白了固定在頂部的實現原理,相信你也一定能弄明白固定在底部的實現原理,所以這里也是為了減少篇幅,提高效率,僅僅介紹固定在頂部的情況:

一開始sticky元素和target元素的狀態是這樣的:

當滾動條慢慢向下,使得網頁向上滾動的時候,sticky元素和target元素在一段滾動距離內狀態并沒有發生變化,一直到這個狀態(滾動條滾動距離為573px):

在這個狀態只要滾動條再往下滾動1px,sticky元素就會被固定在頂部(滾動條滾動距離為574px):

也就是說當target元素的頂部離瀏覽器頂部的距離小于0的時候(target元素的頂部未超出瀏覽器頂部的時候,距離看作大于0),sticky元素就會被固定,所以這就是我們要找的第一個臨界點。然后滾動條繼續向下滾動,只要target元素還在瀏覽器可視區域內,sticky元素就會一直被固定:

直到這個狀態(滾動條滾動距離為1861px):

在這個狀態只要滾動條再往下滾動1px,sticky元素就會取消固定在頂部(滾動條滾動距離為1862px):


顯然,這就是我們要找的第2個臨界點,不過它的判斷條件是:當target元素的底部離瀏覽器頂部的距離小于sticky元素的高度時,sticky元素就會被取消固定。這里為什么是小于sticky元素的高度,而不是小于0,原因是因為基于小于0這個臨界點開發出來的組件,會出現target元素幾乎快從瀏覽器可視區域消失了,但是sticky元素還固定在那的效果:


sticky還把footer的內容給蓋住了,本來是為了方便用戶操作,結果影響了用戶操作,所以得把取消固定這個臨界點提前,而用sticky元素的高度最合適。

通過前面對demo變化過程的拆解,我們已經得到了滾動條一直向下滾動時,sticky狀態變化的兩個臨界點:

1)當target元素的頂部離瀏覽器頂部的距離小于0的時候,sticky元素就會被固定;

2)當target元素的底部離瀏覽器頂部的距離小于sticky元素的高度時,sticky元素就會被取消固定。

綜合這兩個臨界點,可以得出滾動條向下滾動時,sticky元素被固定的滾動范圍的判斷條件是:target元素的頂部離瀏覽器頂部的距離小于0 并且 target元素的底部離瀏覽器頂部的距離大于sticky元素的高度。而且這個判斷條件,同樣適用于滾動條向上滾動的情況,因為滾動條一直向上滾動時,sticky狀態變化的臨界點是:

1)當target元素的底部離瀏覽器頂部的距離大于sticky元素的高度時,sticky元素就會被固定;

2)當target元素的頂部離瀏覽器頂部的距離大于0的時候,sticky元素就會被取消固定。

(這兩個臨界點,其實跟滾動條向下滾動時提到的兩個臨界點,是一個意思,只不過是正話反著說而已)

所以只要得到【target元素的頂部離瀏覽器頂部的距離】,【target元素的底部離瀏覽器頂部的距離】,【sticky元素的高度】這三個值基本上就能實現這個組件了。這三個值中sticky元素的高度由設計圖決定,它從網頁一開始制作就是已知的,在定義組件的時候我們可以從外部傳進去,雖然也能從js去獲取它的高度,不過顯然沒有必要增加額外的計算;另外兩個值【target元素的頂部離瀏覽器頂部的距離】,【target元素的底部離瀏覽器頂部的距離】,我們正好可以利用DOM提供的一個方法來獲取,這個方法是:getBoundingClientRect,這是一個兼容性很好的方法,它的調用方式是:

var target = document.getElementById('main-container');var rect = target.getBoundingClientRect();console.log(rect);

返回一個ClientRect對象,這個對象存儲元素框模型的一些信息,比如它的寬高度(width and height),以及元素框上下邊距離瀏覽器頂部邊緣的距離(top and bottom),左右邊距離瀏覽器左邊緣的距離(left and right):


top跟bottom恰恰就是我們要獲取的【target元素的頂部離瀏覽器頂部的距離】,【target元素的底部離瀏覽器頂部的距離】,而且當框的頂部或底部未超出瀏覽器頂部的時候,top跟bottom都是大于0的值,而當框的頂部或底部超出瀏覽器頂部的時候,top跟bottom是小于0的值:

當我們找到了【target元素的頂部離瀏覽器頂部的距離】,【target元素的底部離瀏覽器頂部的距離】,【sticky元素的高度】這三個值,就可以用代碼來描述前面的判斷條件:

rect.top < 0 && (rect.bottom - stickyHeight) > 0;

(rect表示target元素調用getBoundingClientRect返回的對象,stickyHeight表示sticky元素的高度)

最后為了讓實現思路更加完整,雖然不詳細介紹固定在底部的情況的變化過程,我還是把這種情況的臨界點跟判斷方式補充進來,它的臨界點是(這里列的是滾動條向下滾動時的臨界點):

1)當target元素的頂部離瀏覽器頂部的距離 + sticky元素的高度 小于瀏覽器可視區域的高度時,sticky元素被固定;

2)當target元素的底部離瀏覽器的頂部的距離小于瀏覽器可視區域的高度時,sticky元素被取消固定。

瀏覽器可視區域的高度,可用document.documentElement.clientHeight來獲取,這個屬性也是沒有兼容性問題的,判斷代碼為:

var docClientWidth = document.documentElement.clientHeight;rect.bottom > docClientWidth && (rect.top + stickyHeight) < docClientWidth;

2. 實現細節

1)html結構

固定在頂部的html結構:

<div class="container-fluid sticky-wrapper"><ul id="sticky" data-target="#main-container" class="sticky nav nav-pills"><li role="presentation" class="active"><a href="#">Home</a></li><li role="presentation"><a href="#">Profile</a></li><li role="presentation"><a href="#">Messages</a></li></ul></div><div id="main-container" class="container-fluid"><div class="row">...</div>...</div>

固定在底部的html結構:

<div id="main-container" class="container-fluid"><div class="row">...</div>...</div><div class="container-fluid sticky-wrapper"><ul id="sticky" data-target="#main-container" class="sticky nav nav-pills"><li role="presentation" class="active"><a href="#">Home</a></li><li role="presentation"><a href="#">Profile</a></li><li role="presentation"><a href="#">Messages</a></li></ul></div>

以上#main-container就是我們的target元素,#sticky就是我們的sticky元素,還需要注意兩點:

a. 順序問題,兩種結構中,target元素與sticky的父元素順序位置是反的;

b. sticky元素外面必須包裹一層元素,而且還得給這一層元素設置height屬性:

.sticky-wrapper {margin-bottom: 10px;height: 52px;}

這是因為當sticky元素被固定的時候,它會脫離普通文檔流,所以要利用它的父元素把sticky元素的高度在普通文檔流中撐起來,以免在固定效果出現的時候,target元素的內容出現跳動的情況。

2)固定效果

讓一個元素固定在瀏覽器的某個位置,當然是通過position: fixed來弄,所以可以用兩個css類來實現固定在頂部和固定在底部的效果:

.sticky--in-top,.sticky--in-bottom {position: fixed;z-index: 1000;}.sticky--in-top {top: 0;}.sticky--in-bottom {bottom: 0;}

當我們判斷元素需要被固定在頂部的時候,就給它添加.sticky--in-top的css類;當我們判斷元素需要被固定在底部的時候,就給它添加.sticky--in-bottom的css類。

3)滾動回調

控制sticy元素固定的邏輯顯然要寫在window的scroll事件回調中(有了前面對實現思路以及判斷條件的說明,相信理解下面這段代碼應該會很容易):

固定在頂部的回調邏輯:

$(window).scroll(function() {var rect = $target[0].getBoundingClientRect();if (rect.top < 0 && (rect.bottom - stickyHeight) > 0) {!$elem.hasClass('sticky--in-top') && $elem.addClass('sticky--in-top').css('width', stickyWidth + 'px');} else {$elem.hasClass('sticky--in-top') && $elem.removeClass('sticky--in-top').css('width', 'auto');}});

其中:$target是target元素的jq對象,$elem是sticky元素的jq對象,stickyHeight是sticky元素的高度,stickyWidth是sticky元素的寬度。由于sticky元素固定時,脫離原來的文檔流,需要設置寬度才能顯示跟固定前一樣的寬度。

固定在底部的回調邏輯:

$(window).scroll(function() {var rect = $target[0].getBoundingClientRect(),docClientWidth = document.documentElement.clientHeight; if (rect.bottom > docClientWidth && (rect.top + stickyHeight) < docClientWidth) {!$elem.hasClass('sticky--in-bottom') && $elem.addClass('sticky--in-bottom').css('width', stickyWidth + 'px');} else {$elem.hasClass('sticky--in-bottom') && $elem.removeClass('sticky--in-bottom').css('width', 'auto');}});

這里是為了把回調邏輯說的更清楚才把代碼分成兩份,最后給的實現會把這兩個代碼合并成一份:)

4)函數節流

函數節流通常應用于window的scroll事件,resize事件以及普通元素的mousemove事件,因為這些事件由于鼠標或滾輪操作很頻繁,會導致回調連續觸發,如果回調里面含有DOM操作,這種連續調用就會影響頁面的性能,所以很有必要控制這類回調的執行次數,函數節流就是做這個的,我這里提供了一個很簡單的函數節流實現:

function throttle(func, wait) {var timer = null;return function() {var self = this,args = arguments;if (timer) clearTimeout(timer);timer = setTimeout(function() {return typeof func === 'function' && func.apply(self, args);}, wait);}}

這個函數可以控制func所指定的函數,執行的間隔指定為wait指定的毫秒數,利用它,我們可以把前面的滾動回調改動一下,比如固定在頂部的情況改成:

$(window).scroll(throttle(function() {var rect = $target[0].getBoundingClientRect(),docClientWidth = document.documentElement.clientHeight; if (rect.bottom > docClientWidth && (rect.top + stickyHeight) < docClientWidth) {!$elem.hasClass('sticky--in-bottom') && $elem.addClass('sticky--in-bottom').css('width', stickyWidth + 'px');} else {$elem.hasClass('sticky--in-bottom') && $elem.removeClass('sticky--in-bottom').css('width', 'auto');}}, 50);

其實真正處理回調的是throttle返回的函數,這個返回的函數邏輯少,而且沒有DOM操作,它是會被連續調用的,但是不影響頁面性能,而我們真正處理邏輯的那個函數,也就是傳入throttle的那個函數因為throttle創建的閉包的作用,不會被連續調用,這樣就實現了控制函數執行次數的目的。

5)resize的問題

window resize總是在定義組件的時候帶來問題,因為頁面可視區域的寬高度發生了變化,sticky元素的父容器寬度也可能發生了變化,而且resize的時候不會觸發scroll事件,所以我們需要在resize回調內,刷新sticky元素的寬度以及重新調用固定效果的邏輯,這個相關的代碼就不貼出來了,后面直接看整體實現吧,否則我怕放出來會影響理解??傊畆esize是我們在定義組件的時候肯定要考慮的,不過一般都放到最后來處理,有點算處理BUG之類的工作。

3. 整體實現

代碼比較簡潔:

/*** @param elem: jquery選擇器,用來獲取要被固定的元素* @param opts:* - target: jquery選擇器,用來獲取表示固定范圍的元素* - type: top|bottom,表示要固定的位置* - height: 要固定的元素的高度,由于高度在做頁面時就是確定的并且幾乎不會被DOM操作改變,直接從外部傳入可以除去獲取元素高度的操作* - wait: 滾動事件回調的節流時間,控制回調至少隔多長時間才執行一次* - getStickyWidth:獲取要固定元素的寬度,window resize或者DOM操作會導致固定元素的寬度發生變化,需要這個回調來刷新stickyWidth*/var Sticky = function (elem, opts) {var $elem = $(elem), $target = $(opts.target || $elem.data('target'));if (!$elem.length || !$target.length) return;var stickyWidth, $win = $(window),stickyHeight = opts.height || $elem[0].offsetHeight,rules = {top: function (rect) {return rect.top < 0 && (rect.bottom - stickyHeight) > 0;},bottom: function (rect) {var docClientWidth = document.documentElement.clientHeight;return rect.bottom > docClientWidth && (rect.top + stickyHeight) < docClientWidth;}},type = (opts.type in rules) && opts.type || 'top',className = 'sticky--in-' + type;refreshStickyWidth();$win.scroll(throttle(sticky, $.isNumeric(opts.wait) && parseInt(opts.wait) || 100));$win.resize(throttle(function () {refreshStickyWidth();sticky();}, 50));function refreshStickyWidth() {stickyWidth = typeof opts.getStickyWidth === 'function' && opts.getStickyWidth($elem) || $elem[0].offsetWidth;$elem.hasClass(className) && $elem.css('width', stickyWidth + 'px');}//效果實現function sticky() {if (rules[type]($target[0].getBoundingClientRect())) {!$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth + 'px');} else {$elem.hasClass(className) && $elem.removeClass(className).css('width', 'auto');}}//函數節流function throttle(func, wait) {var timer = null;return function () {var self = this, args = arguments;if (timer) clearTimeout(timer);timer = setTimeout(function () {return typeof func === 'function' && func.apply(self, args);}, wait);}}};

調用方式,固定在頂部的情況(type選項默認為top):

<script>new Sticky('#sticky',{height: 52,getStickyWidth: function($elem){return ($elem.parent()[0].offsetWidth - 30);}});</script>

固定在底部的情況:

<script>new Sticky('#sticky',{height: 52,type: 'bottom',getStickyWidth: function($elem){return ($elem.parent()[0].offsetWidth - 30);}});</script>

還有一個要說明的是,opts的getStickyWidth選項,這個回調用來獲取sticky元素的寬度,為什么要把它放出來,通過外部去獲取寬度,而不是在組件內部通過offsetWidth獲???是因為當sticky元素的外部容器是自適應的時候,sticky元素固定時的寬度不是由sticky元素自己決定的,而是依賴于外部容器的寬度,所以這個寬度只能在外部去獲取,內部獲取不準確。比如上面的代碼中我減了一個30,如果在組件內部獲取的話,我肯定不知道要添加減30這樣一個邏輯。

4. 總結

本文提供了一個很常見的sticky組件實現,實現這個組件的關鍵在于找到控制sticky元素固定與否的關鍵點,同時在實現的時候函數節流跟window resize的問題需要特別注意。

我一直認為對于一些簡單的組件,掌握它的思路,自己去定義比直接從github上去找開源的插件要來的更切實際:

1)代碼可控,不用去閱讀別人的代碼,有問題也能快速修改

2)代碼量小,開源的插件會盡可能多做事,而有些工作你的項目并不一定需要它去做;

3)更貼合項目的實際需求,跟第2點差不多的意思,在已有的思路基礎上,我們能開發出與項目需求完全契合的功能模塊;

4)有助于提高自己的技術水平,增進知識的廣度和深度;

所以有能力造輪子的時候,造造也是很有必要的。

本文雖然在最后提供了整體的組件實現,但是并不是建議拿來就用,否則前面大篇幅地去介紹實現思路就沒有必要了,我只要放個github地址即可,思路遠比實現重要。我最近幾篇博客都是在分享思路,而不是分享某個具體的實現,思路這種抽象的東西是通用的,理解前它不是你的,理解后它就存在于腦袋里,任何時候都可以拿來就用,我提供的思路也同樣來自于我對其它博客其它插件源碼學習之后的思考與總結。

補充于說明:

本文實現有不足,不完美的地方,請在了解本文相關內容后,移步閱讀《sticky組件的改進實現》了解更佳的實現。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美亚洲国产精品| 日韩中文字幕在线视频| 国产精品com| 欧美在线视频网| 亚洲色图av在线| 57pao成人永久免费视频| 亚洲色图狂野欧美| 国产伦精品一区二区三区精品视频| 欧美日韩另类在线| 91国内产香蕉| 国产精品美女主播| 国产成人综合久久| 疯狂蹂躏欧美一区二区精品| 国产精品一区二区三| 成人午夜在线观看| 欧美激情女人20p| 亚洲精品美女在线观看| 久久99精品久久久久久琪琪| 992tv在线成人免费观看| 亚洲一级一级97网| 亚洲jizzjizz日本少妇| 欧美日韩加勒比精品一区| 欧美国产日韩在线| 精品在线小视频| 国产成人免费av电影| 在线成人激情黄色| 亚洲国产欧美日韩精品| 欧美大片免费观看在线观看网站推荐| 91在线观看免费高清完整版在线观看| 欧美在线免费观看| 久久99精品视频一区97| 国产成人涩涩涩视频在线观看| 久久久久久久一区二区三区| 福利二区91精品bt7086| 欧美性猛交xxxxx水多| 美女扒开尿口让男人操亚洲视频网站| 成人综合国产精品| 亚洲欧美精品suv| 久久中文字幕在线| 色99之美女主播在线视频| 精品少妇v888av| 精品久久久久久国产| 欧美激情按摩在线| 欧美在线xxx| 日韩av快播网址| 久久韩剧网电视剧| 欧美激情精品久久久| 超薄丝袜一区二区| 日韩在线观看成人| 在线国产精品播放| 精品久久中文字幕| 国产美女精彩久久| 国产一区视频在线播放| 大荫蒂欧美视频另类xxxx| 久久精品视频在线播放| 色系列之999| 日本精品视频在线| 神马国产精品影院av| 91精品国产综合久久男男| 国产亚洲a∨片在线观看| 亚洲欧美一区二区三区四区| 黄网站色欧美视频| 97人人模人人爽人人喊中文字| 国产精品h在线观看| 欧美丝袜美女中出在线| 欧美日韩在线视频一区二区| 欧美激情视频播放| 亚洲第一视频网站| 国产欧美精品一区二区三区介绍| 亚洲精品欧美日韩专区| 国产成人精品久久| 欧美精品激情在线| 黑人与娇小精品av专区| 中文字幕亚洲综合| 不卡av日日日| 66m—66摸成人免费视频| 欧美日本高清一区| 在线色欧美三级视频| 日韩成人网免费视频| 91精品国产91久久| 粉嫩av一区二区三区免费野| 国产精品香蕉在线观看| 97热精品视频官网| 91牛牛免费视频| 国产精品日日做人人爱| 欧美片一区二区三区| 欧美剧在线观看| 国产男人精品视频| 国产69久久精品成人看| 久久视频这里只有精品| 91精品国产自产在线老师啪| 亚洲欧美在线x视频| 国产精品久久91| 欧美贵妇videos办公室| 久久精品亚洲国产| 欧美电影第一页| 亚洲人成免费电影| 国产精品视频最多的网站| 岛国av一区二区三区| 日韩av综合网站| 91在线网站视频| 亚洲男人天堂古典| 麻豆精品精华液| 日韩视频精品在线| 欧美床上激情在线观看| 久久精品久久久久电影| 亚洲精品电影网在线观看| 国精产品一区一区三区有限在线| 日韩免费观看网站| 成人免费视频xnxx.com| 韩国欧美亚洲国产| 亚洲欧美中文日韩在线v日本| 国产在线一区二区三区| 5278欧美一区二区三区| 欧美视频在线观看免费| 国产美女久久久| 国产精品久久久久久av| 亚洲无限av看| 国产一区二区三区精品久久久| 国产欧美日韩精品丝袜高跟鞋| 92版电视剧仙鹤神针在线观看| 久久久亚洲影院你懂的| 国产视频一区在线| 成人免费视频在线观看超级碰| 色婷婷久久av| 欧美多人乱p欧美4p久久| 欧美精品生活片| 欧美乱大交xxxxx另类电影| 久久韩剧网电视剧| 成人日韩在线电影| 欧美精品成人91久久久久久久| 国产精品无av码在线观看| 97成人在线视频| 亚洲激情免费观看| 亚洲美女性视频| 国产精品美女久久久久久免费| 国产精品永久免费视频| 日韩精品亚洲精品| 精品亚洲夜色av98在线观看| 亚洲国产精品久久久久秋霞不卡| 亚洲xxxx18| 亚洲黄色有码视频| 韩国v欧美v日本v亚洲| 91中文精品字幕在线视频| 欧美日韩国产一中文字不卡| 亚洲a区在线视频| 亚洲午夜国产成人av电影男同| 亚洲精品欧美极品| 欧美电影免费观看电视剧大全| 精品中文字幕久久久久久| 欧美精品免费在线| 欧美亚州一区二区三区| 亚洲欧美日韩直播| 91欧美日韩一区| 热99精品只有里视频精品| 久久久视频精品| 日韩在线免费视频| 亚洲人精品午夜在线观看| 亚洲国产91色在线| 久久精品电影一区二区| 久久天天躁狠狠躁老女人| 成人欧美一区二区三区在线湿哒哒| 欧美综合第一页| 国产区亚洲区欧美区|