1. 程式人生 > >用一個案例介紹jQuery外掛的使用和寫法

用一個案例介紹jQuery外掛的使用和寫法

       我們在做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為作用域,這樣定義了一個自呼叫匿名函式,全域性空間就不能訪問其中定義的區域性變量了。
其中
jQuery,window,document作為實參傳遞給匿名函式,外掛內部就可以使用$作為jQuery的別名了。
;(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();
				}