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

首頁 > 編程 > JavaScript > 正文

淺談Sticky組件的改進實現

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

在上一篇文章使用getBoundingClientRect方法實現簡潔的sticky組件的方法介紹了一個sticky組件的簡潔實現,經過這兩天的思考,發現上次提供的實現還有較多不足的地方,另外跟別的網站上實現的效果在取消固定的時候也有一些不同,上次提供的取消固定的處理方式不好,本文在上文的基礎上,提供一個改進版的sticky組件,功能更加完善,希望您有興趣閱讀。

1. 舊版本的問題

上一個sticky組件的實現中,有多個問題存在:

第一,從sticky的效果上來說,sticky元素在固定前后,不會變化的是相對瀏覽器左邊的位置以及sticky元素的整體寬度,可能會變化的是相對瀏覽器頂部或底部的位置和sticky元素的高度,而上文提供的實現中把后面兩個會變化的值都當成了不變的值。為什么在固定的時候top值或bottom值就一定是0?當然可以不是0阿,比如top: 20px,bottom: 15px,在某些場景里,加上一些這樣的偏移,sticky的效果會更好看,比如bootstrap官方文檔中用到的affix組件實例(這個組件的功能跟本文實現的sticky組件是差不多的):

它就把固定的時候,相對瀏覽器頂部的位置設置成了top: 20px。sticky元素的高度也是,為了在固定的時候顯示更好看的效果,調整原來的Line-height或者padding-top等更高度有關的屬性,也是非常常見的需求,比如天貓花唄的這個頁面,這塊內容就用到了sticky組件:

固定前,sticky元素的高度是:

固定后,sticky元素的高度是:

第二,在取消固定的時候,以sticky元素固定在頂部為例,上文提供的實現是在target元素跟瀏覽器頂部的距離小于stickyHeight的時候,就直接取消sticky元素的position: fixed屬性,sticky元素立馬被還原到普通文檔流中,效果是:

它是在臨界點的時候立馬就消失的,而天貓花唄的那個效果就不是這樣:

它在臨界點的時候并不是立即消失,而是重新去調整sticky元素的top值,讓它配合著滾動條一起跟隨網頁主體內容一起向上滾動:

從體驗上來說,顯然天貓花唄的這個效果更好一點,從功能上來說,上文提供的實現有一個致命的缺點:就是當sticky元素的高度非常大,超出了瀏覽器可視區域的高度的時候,會出現不管你怎么滾動,都無法瀏覽全sticky元素所有內容的BUG,有興趣的可以拿上次實現的代碼在自己博客的側邊欄上試一試。我試過發現了這個問題,所以才想要改進sticky組件:(
第三,上次的實現還有幾處不足的地方:

1)documentElement.clientHeight沒有做緩存,導致每次判斷臨界點時都要去重新獲?。?

2)滾動回調間隔的默認值太大,應該再設置小一點,這次用的是5,bootstrap用的是1,只有這樣才能保證效果流暢;

3)有的場景可能不需要resize的時候重新設置sticky元素的寬度,應該加個選項來控制;

4)在sticky元素固定和取消固定的時候,應該提供回調函數,以便其它組件依賴這個組件的時候可以在關鍵點做些事情。

2. 如何改進

組件的選項重新定義了一下:

var DEFAULTS = {target: '', //target元素的jq選擇器type: 'top', //固定的位置,top | bottom,默認為top,表示固定在頂部wait: 5, //scroll事件回調的間隔stickyOffset: 0, //固定時距離瀏覽器可視區頂部或底部的偏移,用來設置top跟bottom屬性的值,默認為0isFixedWidth: true, //sticky元素寬度是否固定,默認為true,如果是自適應的寬度,需設置為falsegetStickyWidth: undefined, //用來獲取sticky元素寬度的回調,在不傳該參數的情況下,stickyWidth將設置為sticky元素的offsetWidthunStickyDistance: undefined, //該參數決定sticky元素何時進入dynamicSticky狀態onSticky: undefined, ///sticky元素固定時的回調onUnSticky: undefined ///sticky元素取消固定時的回調};

加粗的幾個是新增或有修改的,去掉了原來的height,用unStickyDistance來替代。固定時候相對瀏覽器頂部或底部的位置,用stickyOffset來指定,這樣在.sticky--in-top或.sticky--in-bottom的css里就不用再寫top或bottom屬性值了。isFixedWidth如果為false,才會去添加resize時刷新sticky元素寬度的回調:


!opts.isFixedWidth && $win.resize(throttle(function () {setStickyWidth();$elem.hasClass(className) && $elem.css('width', stickyWidth);sticky();}, opts.wait));

本次實現相比上次,麻煩的是取消固定時的邏輯處理,上次sticky元素只有2種狀態,sticky或者unsticky,這次不一樣,sticky狀態里面又分成了staticSticky和dynamicSticky,前者表示top或bottom值不變的sticky狀態,后者表示top或bottom值會變化的sticky狀態,其實后者對應的就是快要取消固定的時候那段范圍,為了更清晰地解決這個問題,將原來判斷臨界點以及在不同臨界點做不同處理的代碼重構成下面這個樣子:

setSticky = function () {!$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth)&& (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target));return true;},states = {staticSticky: function () {setSticky() && $elem.css(opts.type, opts.stickyOffset);},dynamicSticky: function (rect) {setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect));},unSticky: function () {$elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '')&& (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target));}},rules = {top: {getState: function (rect) {if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky';else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky';else return 'unSticky';},getDynamicOffset: function (rect) {return -(unStickyDistance - rect.bottom);}},bottom: {getState: function (rect) {if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky';else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky';else return 'unSticky';},getDynamicOffset: function (rect) {return -(unStickyDistance + rect.top - docClientHeight);}}}$win.scroll(throttle(sticky, opts.wait));function sticky() {var rect = $target[0].getBoundingClientRect(),curState = rules[opts.type].getState(rect);states[curState](rect);}

有點狀態模式的思想在里面,不過更簡潔。當我寫出這個代碼的時候,其實是很想用之前了解的狀態機來寫的,我想過用狀態機來寫肯定是可以實現的,不過為了少引用一個類庫就算了,等哪天想實踐狀態機的時候再來嘗試一把。

整體實現如下:

var Sticky = (function ($) {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);}}var DEFAULTS = {target: '', //target元素的jq選擇器type: 'top', //固定的位置,top | bottom,默認為top,表示固定在頂部wait: 5, //scroll事件回調的間隔stickyOffset: 0, //固定時距離瀏覽器可視區頂部或底部的偏移,用來設置top跟bottom屬性的值,默認為0isFixedWidth: true, //sticky元素寬度是否固定,默認為true,如果是自適應的寬度,需設置為falsegetStickyWidth: undefined, //用來獲取sticky元素寬度的回調,在不傳該參數的情況下,stickyWidth將設置為sticky元素的offsetWidthunStickyDistance: undefined, //該參數決定sticky元素何時進入dynamicSticky狀態onSticky: undefined, ///sticky元素固定時的回調onUnSticky: undefined ///sticky元素取消固定時的回調};return function (elem, opts) {var $elem = $(elem);opts = $.extend({}, DEFAULTS, opts || {}, $elem.data() || {});var $target = $(opts.target);if (!$elem.length || !$target.length) return;var stickyWidth,setStickyWidth = function () {stickyWidth = typeof opts.getStickyWidth === 'function' && opts.getStickyWidth($elem) || $elem[0].offsetWidth;},docClientHeight = document.documentElement.clientHeight,unStickyDistance = opts.unStickyDistance || $elem[0].offsetHeight,setSticky = function () {!$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth)&& (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target));return true;},states = {staticSticky: function () {setSticky() && $elem.css(opts.type, opts.stickyOffset);},dynamicSticky: function (rect) {setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect));},unSticky: function () {$elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '')&& (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target));}},rules = {top: {getState: function (rect) {if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky';else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky';else return 'unSticky';},getDynamicOffset: function (rect) {return -(unStickyDistance - rect.bottom);}},bottom: {getState: function (rect) {if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky';else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky';else return 'unSticky';},getDynamicOffset: function (rect) {return -(unStickyDistance + rect.top - docClientHeight);}}},className = 'sticky--in-' + opts.type,$win = $(window);setStickyWidth();$win.scroll(throttle(sticky, opts.wait));!opts.isFixedWidth && $win.resize(throttle(function () {setStickyWidth();$elem.hasClass(className) && $elem.css('width', stickyWidth);sticky();}, opts.wait));$win.resize(throttle(function () {docClientHeight = document.documentElement.clientHeight;}, opts.wait));function sticky() {var rect = $target[0].getBoundingClientRect(),curState = rules[opts.type].getState(rect);states[curState](rect);}}})(jQuery);

難理解的可能是getState的那個方法的邏輯,這部分的一些思路在上上篇博客有比較詳細的說明。

3. 博客側邊欄應用說明

首先得把本次的實現粘貼到博客設置頁腳html文本域里面去,然后加入下面的代碼來初始化:

var timer = setInterval(function(){if($('#blogCalendar').length && $('#profile_block').length && $('#sidebar_search').length) {new Sticky('#sideBar', {target: '#main',onSticky: function($elem, $target){$target.css('min-height',$elem.outerHeight());$elem.css('left', '65px');},onUnSticky: function($elem, $target){$target.css('min-height','');$elem.css('left', '');}});}},100);

使用timer是因為側邊欄的內容都是ajax加載,又不可能在這些ajax請求時候添加回調,只能通過它們返回的內容來判斷側邊欄是否加載完畢。

4. 總結

這周末琢磨了下如何改進sticky組件,加上寫這篇文章,花了大半天的時間,好歹現在這個sticky組件的功能跟實現能讓自己有點滿意的感覺了,上次寫完總覺得怪怪的,好像缺點什么,原來是因為還差這么多東西。現在這個組件還只是能實現固定和取消固定的效果,對于實際工作而言,這個層級的效果可能還不夠,網上常見的那種在固定的同時支持導航滾動或者tab導航的功能也很常見,下篇文章會介紹基于本文的sticky組件,如何實現navScrollSticky以及tabSticky組件,敬請關注。
感謝您的閱讀:)

補充說明:
IE跟火狐里面,在刷新頁面的時候,如果刷新前頁面有滾動,刷新的操作雖然還會把頁面的滾動位置設置成刷新的位置,但是不會觸發scroll事件,所以必須在組件初始化之后立即調用一次sticky函數:

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久精品国产欧美亚洲人人爽| 国产免费一区视频观看免费| 亚洲老头老太hd| 国产精品视频一区二区高潮| 久久综合久久八八| 欧美午夜精品久久久久久人妖| 欧美午夜电影在线| 97香蕉久久夜色精品国产| 日韩中文在线中文网在线观看| 久久久久久久久网站| 激情久久av一区av二区av三区| 国产精品视频免费观看www| 在线观看日韩视频| 伊人伊人伊人久久| 操91在线视频| 亚洲日韩第一页| 欧美激情三级免费| 日韩国产在线看| 成年人精品视频| 北条麻妃99精品青青久久| 91高清在线免费观看| 91日本在线观看| 精品亚洲一区二区三区在线播放| 欧美高清在线观看| 最近2019好看的中文字幕免费| 国产在线观看精品一区二区三区| 日韩**中文字幕毛片| 精品视频在线观看日韩| 色中色综合影院手机版在线观看| 亚洲欧美视频在线| 国产精品久久久久久久一区探花| 亚洲欧美精品一区| 亚洲欧美日韩国产成人| 亚洲激情在线观看视频免费| 久久久久久久久久av| 成人久久18免费网站图片| 欧美日韩国产中字| 国产精品极品美女粉嫩高清在线| 久久综合亚洲社区| 精品无人区太爽高潮在线播放| 九色精品美女在线| 欧美性视频精品| 国产精品一区电影| 欧美最猛性xxxxx免费| 亚洲已满18点击进入在线看片| 久久久女人电视剧免费播放下载| 国产日韩在线一区| 欧美一级大片在线免费观看| 久久久久女教师免费一区| 日韩欧美在线国产| 久久福利视频网| 国产精品极品美女粉嫩高清在线| 日韩av影视在线| 欧美最顶级的aⅴ艳星| 91麻豆桃色免费看| 亚洲色图综合网| 懂色aⅴ精品一区二区三区蜜月| 亚洲色图第三页| 黄色成人av网| 国产欧美精品一区二区三区介绍| 日本一区二三区好的精华液| 欧美日韩人人澡狠狠躁视频| 日韩女优在线播放| 激情av一区二区| 精品久久久久久久久国产字幕| 欧美国产日韩xxxxx| 国产91精品久久久| 亚洲www在线观看| 亚洲国产免费av| 日韩在线播放av| 久久国产精品久久久| 日韩精品视频在线观看免费| 亚洲精品短视频| 亚洲人成电影在线观看天堂色| 亚洲欧美日韩精品| 日韩人体视频一二区| 91美女福利视频高清| 国产99视频在线观看| 亚洲淫片在线视频| 精品亚洲精品福利线在观看| 2020欧美日韩在线视频| 国内自拍欧美激情| 国产精品一区二区女厕厕| 成人激情av在线| 欧美日韩一区二区免费视频| 欧美华人在线视频| 欧美一级大片视频| 粉嫩av一区二区三区免费野| 欧美日韩国产中文精品字幕自在自线| 久久免费成人精品视频| 久久亚洲一区二区三区四区五区高| 国产精品视频资源| 九九精品在线播放| 视频一区视频二区国产精品| 欧美性xxxxx极品娇小| 日韩成人久久久| 日韩精品中文在线观看| 亚洲男人av电影| 国产精品成人av在线| 日韩视频永久免费观看| 日韩国产激情在线| 性欧美办公室18xxxxhd| 亚洲精品一二区| 日韩成人在线视频| 成人精品在线观看| 国产美女久久久| 日韩女优在线播放| 亚洲欧美在线一区| 欧美性猛交xxxx免费看| 97视频在线观看免费高清完整版在线观看| 欧美成人亚洲成人| 国产精品久久久久久久天堂| 国产精品久久77777| 亚洲欧美日韩国产精品| 久久亚洲综合国产精品99麻豆精品福利| 最近2019中文字幕第三页视频| 亚洲成人动漫在线播放| 欧美性猛交xxxx黑人| 91久久久久久| 欧美电影免费观看高清完整| 日本一区二区三区在线播放| 在线观看精品自拍私拍| 欧美成人午夜激情视频| 2019中文字幕在线观看| 538国产精品一区二区免费视频| 日本不卡视频在线播放| 久久久亚洲影院| 北条麻妃99精品青青久久| 欧美日韩国产精品一区二区三区四区| 欧美激情2020午夜免费观看| 欧美亚洲成人xxx| 久久精品国产亚洲一区二区| 日韩欧美精品在线观看| 尤物精品国产第一福利三区| 性色av香蕉一区二区| 亚洲欧美自拍一区| 欧美成人精品影院| 欧美精品日韩www.p站| 国产精品91久久久久久| 国产精品日韩欧美大师| 国产成人精品一区二区三区| 91精品久久久久久久| 成人午夜激情网| 中文字幕av一区二区三区谷原希美| 欧洲中文字幕国产精品| 国产精品视频久| 欧美黑人一区二区三区| 欧美国产精品va在线观看| 不卡伊人av在线播放| 亚洲成人中文字幕| 视频直播国产精品| 久久久久北条麻妃免费看| 2019亚洲日韩新视频| 91性高湖久久久久久久久_久久99| 欧美成人免费视频| 丝袜亚洲欧美日韩综合| 日韩av一区在线| 久久久噜噜噜久久中文字免| 国产精品自拍网| 国产精品第一第二| 国产日本欧美一区| 国产成人精品一区二区| 欧美贵妇videos办公室| 自拍偷拍亚洲在线|