用一個案例介紹jQuery外掛的使用和寫法
阿新 • • 發佈:2019-02-13
我們在做web的時候都會用到很多jQuery外掛,這些外掛可以很方便的使用。但對於初學者來說想要修改外掛中的一些功能,或者想要自定義外掛卻不是容易的事情。自己也剛好在學習這部分的知識,這裡用一個案例來介紹jQuery外掛的使用和寫法。
就拿單頁導航這個外掛來舉例吧,one-page-nav寫的比較規範程式碼也比較短。其他jQuery外掛大同小異,學會結構和規範之後自己也可以自定義外掛,後面也會講解對該外掛功能的改寫,從 會使用-》看懂-》改寫-》自定義外掛有一個循序漸進的過程。
地址:github:https://github.com/davist11/jQuery-One-Page-Nav
原外掛內容:
;(function($, window, document, undefined){ // our plugin constructor var OnePageNav = function(elem, options){ this.elem = elem; this.$elem = $(elem); this.options = options; this.metadata = this.$elem.data('plugin-options'); this.$win = $(window); this.sections = {}; this.didScroll = false; this.$doc = $(document); this.docHeight = this.$doc.height(); }; // the plugin prototype OnePageNav.prototype = { defaults: { navItems: 'a', currentClass: 'current', changeHash: false, easing: 'swing', filter: '', scrollSpeed: 750, scrollThreshold: 0.5, begin: false, end: false, scrollChange: false }, init: function() { // Introduce defaults that can be extended either // globally or using an object literal. this.config = $.extend({}, this.defaults, this.options, this.metadata); this.$nav = this.$elem.find(this.config.navItems); //Filter any links out of the nav if(this.config.filter !== '') { this.$nav = this.$nav.filter(this.config.filter); } //Handle clicks on the nav this.$nav.on('click.onePageNav', $.proxy(this.handleClick, this)); //Get the section positions this.getPositions(); //Handle scroll changes this.bindInterval(); //Update the positions on resize too this.$win.on('resize.onePageNav', $.proxy(this.getPositions, this)); return this; }, adjustNav: function(self, $parent) { self.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass); $parent.addClass(self.config.currentClass); }, bindInterval: function() { var self = this; var docHeight; self.$win.on('scroll.onePageNav', function() { self.didScroll = true; }); self.t = setInterval(function() { docHeight = self.$doc.height(); //If it was scrolled if(self.didScroll) { self.didScroll = false; self.scrollChange(); } //If the document height changes if(docHeight !== self.docHeight) { self.docHeight = docHeight; self.getPositions(); } }, 250); }, getHash: function($link) { return $link.attr('href').split('#')[1]; }, getPositions: function() { var self = this; var linkHref; var topPos; var $target; self.$nav.each(function() { linkHref = self.getHash($(this)); $target = $('#' + linkHref); if($target.length) { topPos = $target.offset().top; self.sections[linkHref] = Math.round(topPos); } }); }, getSection: function(windowPos) { var returnValue = null; var windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold); for(var section in this.sections) { if((this.sections[section] - windowHeight) < windowPos) { returnValue = section; } } return returnValue; }, handleClick: function(e) { var self = this; var $link = $(e.currentTarget); var $parent = $link.parent(); var newLoc = '#' + self.getHash($link); if(!$parent.hasClass(self.config.currentClass)) { //Start callback if(self.config.begin) { self.config.begin(); } //Change the highlighted nav item self.adjustNav(self, $parent); //Removing the auto-adjust on scroll self.unbindInterval(); //Scroll to the correct position self.scrollTo(newLoc, function() { //Do we need to change the hash? if(self.config.changeHash) { window.location.hash = newLoc; } //Add the auto-adjust on scroll back in self.bindInterval(); //End callback if(self.config.end) { self.config.end(); } }); } e.preventDefault(); }, scrollChange: function() { var windowTop = this.$win.scrollTop(); var position = this.getSection(windowTop); var $parent; //If the position is set if(position !== null) { $parent = this.$elem.find('a[href$="#' + position + '"]').parent(); //If it's not already the current section if(!$parent.hasClass(this.config.currentClass)) { //Change the highlighted nav item this.adjustNav(this, $parent); //If there is a scrollChange callback if(this.config.scrollChange) { this.config.scrollChange($parent); } } } }, scrollTo: function(target, callback) { var offset = $(target).offset().top; $('html, body').animate({ scrollTop: offset }, this.config.scrollSpeed, this.config.easing, callback); }, unbindInterval: function() { clearInterval(this.t); this.$win.unbind('scroll.onePageNav'); } }; OnePageNav.defaults = OnePageNav.prototype.defaults; $.fn.onePageNav = function(options) { return this.each(function() { new OnePageNav(this, options).init(); }); }; })( jQuery, window , document );
先來看該外掛實現的功能有,接下來再講它是如何實現的。
1:點選導航欄的某一項,內容導航到相應項。當前項的<li>中增加class="current"。
2:滑鼠滾動到相應內容時,導航欄中class="current"會切換。
3:其他功能比如說滾動速度和回撥函式等。
外掛的封裝
我們去掉內容,只看這部分。有很多的括號和引數,最開始看的時候內心是拒絕的,完全不知道這麼多括號是幹什麼的,慢慢學下去會發現還是很有意思的。這裡是利用了閉包的特性,既可以避免內部臨時變數影響全域性空間,又可以在外掛內部繼續使用$作為jQuery的別名。 為了更好的相容性,開始有一個分號,否則壓縮的時候可能出現問題。首先定義一個匿名函式function(){},然後用括號括起來,最後通過()這個運算子來執行。js是以function為作用域,這樣定義了一個自呼叫匿名函式,全域性空間就不能訪問其中定義的區域性變量了。;(function($, window, document, undefined){
//這裡放置程式碼
})( jQuery, window , document );
向jQuery的名稱空間新增新的方法
jQuery.fn即$.fn是指jQuery的名稱空間,所有的物件方法應當附加到就jQuery.fn物件上。這樣寫之後外部就可以通過這種方式呼叫它$('#nav').onePageNav(option)我們遵循jQuery的規範,外掛應該返回一個jQuery物件,以保證外掛的可鏈式操作。假設$('.nav')可能是一個數組,可以通過this.each來遍歷所有的元素。這種情況可以用於一個頁面有多個導航的時候。 $.fn.onePageNav = function(options) {
return this.each(function() {
new OnePageNav(this, options).init();
});
};
定義OnePageNav物件
這裡定義一個物件OnePageNav,在呼叫外掛的時候用new可以建立一個原物件的例項物件。我們注意到引數加了$符號的表示的是jQuery物件,沒有加$符號表示的是DOM物件,這是一個 很好的習慣,在使用它們的時候就不需要做DOM物件和jQuery物件之間的轉換。
var OnePageNav = function(elem, options){
this.elem = elem;
this.$elem = $(elem);
this.options = options;
this.metadata = this.$elem.data('plugin-options');
this.$win = $(window);
this.sections = {};
this.didScroll = false;
this.$doc = $(document);
this.docHeight = this.$doc.height();
};
設定預設引數
所有的外掛幾乎都會有自己的預設引數,所以在呼叫的時候即使不傳遞option也可以,在OnePageNav 的原型中的預設引數如下。
defaults: {
navItems: 'a', //預設<a>標籤
currentClass: 'current', //預設當前標籤的樣式名
changeHash: false,
easing: 'swing',
filter: '', //標籤過濾
scrollSpeed: 750, //速度
scrollThreshold: 0.5, //佔面積比
begin: false, //開始回撥函式
end: false, //結束回撥函式
scrollChange: false //市場改變的回撥函式
},
初始化
注意到 $.fn.onePageNav方法中的這句話new OnePageNav(this, options).init(); 呼叫了OnePageNav的init方法進行初始化,現在來看看初始化都做了什麼工作。可以概括為這幾部分1、extend 函式用於將一個或多個物件的內容合併到目標物件,把預設設定和自定義設定合併起來。2、將標籤進行 過濾 ,比如像下面這樣我們要過濾掉最後一個標籤,可以在option中加入 filter: ':not(.exception)', 3、給過濾後的標籤繫結click方法,點選click可以scroll到對應的內容 。4、獲取標籤對應內容的位置,並都放在this.sections中 。5、新增一個間隔性觸發定時器,目的是為了在滑動滑鼠滾輪的時候根據當前顯示內容,更改導航標籤的class=current。6、每次窗體大小改變時重新獲取標籤對應內容的位置。 <ul id="nav"> <li><a href="#nr">內容一</a></li> <li><a href="#nt">內容二</a></li> <li><a href="#ny">內容三</a></li> <li><a href="#nu">內容四</a></li> <li><a class="exception" href="#top">返回頂部</a></li> </ul> init: function() {
this.config = $.extend({}, this.defaults, this.options, this.metadata);
this.$nav = this.$elem.find(this.config.navItems);
if(this.config.filter !== '') {
this.$nav = this.$nav.filter(this.config.filter);
}
this.$nav.on('click.onePageNav', $.proxy(this.handleClick, this));
this.getPositions();
this.bindInterval();
this.$win.on('resize.onePageNav', $.proxy(this.getPositions, this));
return this;
},
功能的實現
其他的方法都是新增到OnePageNav.prototype上的,比較好理解,可以自己看程式碼。這裡特別說一下回調函式。比如說下面這句話,預設值是 begin: false, 如果呼叫的時候option設定了 begin: function() {//I get fired when the animation is starting
},
每次單擊導航標籤的時候就會呼叫這個方法。如果沒有設定begin,自然也不會出問題。
if(self.config.begin) {
self.config.begin();
}