我们在做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 );


jquery formate插件 简单的jquery插件实例_jquery

先来看该插件实现的功能有,接下来再讲它是如何实现的。

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          //市场改变的回调函数
        },
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();
				}