首頁 web前端 js教程 淺談Sticky組件的改進實作_javascript技巧

淺談Sticky組件的改進實作_javascript技巧

May 16, 2016 pm 03:10 PM

在上一篇文章使用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属性的值,默认为0
isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false
getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth
unStickyDistance: 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属性的值,默认为0
isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false
getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth
unStickyDistance: 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函數:

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何創建和發布自己的JavaScript庫? 如何創建和發布自己的JavaScript庫? Mar 18, 2025 pm 03:12 PM

文章討論了創建,發布和維護JavaScript庫,專注於計劃,開發,測試,文檔和促銷策略。

如何在瀏覽器中優化JavaScript代碼以進行性能? 如何在瀏覽器中優化JavaScript代碼以進行性能? Mar 18, 2025 pm 03:14 PM

本文討論了在瀏覽器中優化JavaScript性能的策略,重點是減少執行時間並最大程度地減少對頁面負載速度的影響。

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

如何使用瀏覽器開發人員工具有效調試JavaScript代碼? 如何使用瀏覽器開發人員工具有效調試JavaScript代碼? Mar 18, 2025 pm 03:16 PM

本文討論了使用瀏覽器開發人員工具的有效JavaScript調試,專注於設置斷點,使用控制台和分析性能。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

如何使用源地圖調試縮小JavaScript代碼? 如何使用源地圖調試縮小JavaScript代碼? Mar 18, 2025 pm 03:17 PM

本文說明瞭如何使用源地圖通過將其映射回原始代碼來調試JAVASCRIPT。它討論了啟用源地圖,設置斷點以及使用Chrome DevTools和WebPack之類的工具。

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

See all articles