在我們日常開發工作中,當我們需要某種界面效果的時候,一般會到網上搜索一些jquery插件,基本都能搜到,而且都能達到我們的要求。
但是,我們有沒有想過,或者有沒有思考過這樣的插件是如何開發的呢?光會用不行啊。
O(∩_∩)O哈哈~,今天就一起來探究一下jQuery插件的開發,以及常用的方法,和別人的一些經驗。
總的來說,jQuery插件開發方式主要有三種:
1、通過$.extend()來擴展jQuery
2、通過$.fn 向jQuery添加新的方法
3、通過$.widget()應用jQuery UI的部件工廠方式創建
通常我們使用第二種方法來進行簡單插件開發,當然這里的“簡單“僅僅是相對於第三種方式而言的^_^。
第一種方相比來說比較簡單,僅僅是在jQuery命名空間添加了一個靜態方法而已,使用的時候只需要這樣:$.myfunction()
第三種對於創建復雜而又靈活的UI組件是很強大的。今天這里暫不做討論。
我們總是站在巨人的肩膀上,直插件開發這塊,已有前人總結了一些經驗,也可以說是一些約定俗成的規范和格式,我們可以直接借鑒參考。
A Lightweight Start 模式
讓我們用一些遵循了(包括那些在jQuery 插件創作指導中的)最佳實踐的基礎的東西來開始我們針對插件模式的深入探討。這一模式對於插件開發的新手和只想要實現一些簡單的東西(例如工具插件)的人來說是理想的。A Lightweight Start 使用到了下面這些東西:
- 諸如分號放置在函數調用之前這樣一些通用的最佳實踐(我們將在下面的注釋中解釋為什么要這樣做)
- window,document,undefined作為參數傳入。
- 基本的默認對象。
- 一個簡單的針對跟初始化創建和要一起運作的元素的賦值相關的邏輯的插件構造器。
- 擴展默認的選項。
- 圍繞構造器的輕量級的封裝,它有助於避免諸如實例化多次的問題。
- 堅持最大限度可讀性的jQuery核心風格的指導方針。
至於該模式的演進過程和由來,請參考這篇文章:jQuery插件開發之前世今生
最后,還給出了 a lightweight start模式的代碼格式,以及使用方法:
插件格式:

/*! * jQuery lightweight plugin boilerplate * Original author: @ajpiano * Further changes, comments: @addyosmani * Licensed under the MIT license */ // the semi-colon before the function invocation is a safety // net against concatenated scripts and/or other plugins // that are not closed properly. ;(function ( $, window, document, undefined ) { // undefined is used here as the undefined global // variable in ECMAScript 3 and is mutable (i.e. it can // be changed by someone else). undefined isn't really // being passed in so we can ensure that its value is // truly undefined. In ES5, undefined can no longer be // modified. // window and document are passed through as local // variables rather than as globals, because this (slightly) // quickens the resolution process and can be more // efficiently minified (especially when both are // regularly referenced in our plugin). // Create the defaults once var pluginName = "defaultPluginName", defaults = { propertyName: "value" }; // The actual plugin constructor function Plugin( element, options ) { this.element = element; // jQuery has an extend method that merges the // contents of two or more objects, storing the // result in the first object. The first object // is generally empty because we don't want to alter // the default options for future instances of the plugin this.options = $.extend( {}, defaults, options) ; this._defaults = defaults; this._name = pluginName; this.init(); } Plugin.prototype.init = function () { // Place initialization logic here // We already have access to the DOM element and // the options via the instance, e.g. this.element // and this.options }; // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[pluginName] = function ( options ) { return this.each(function () { if ( !$.data(this, "plugin_" + pluginName )) { $.data( this, "plugin_" + pluginName, new Plugin( this, options )); } }); } })( jQuery, window, document );
這個格式基本上可以認為是固定的,大概看一下:
插件使用方法:
$("#elem").defaultPluginName({ propertyName: "a custom value" });
上面大概介紹了jquery插件的設計方法及一般模式,下面會給出例子,看看實際是怎么操作的:
例子1:自定義jQuery插件之開關式的checkbox
這種開關在web和移動端web上使用廣泛。
提示:
1>上面的規范里面已經給出了用法,一般用class名稱作為選擇,來對某個元素實施方法。這樣可以實現控件和jquery插件方法之間的“輕聯系”,避免對頁面其他元素產生影響。
2>另外,checkbox元素最重要的就是是否被選中這個狀態值,所以這里需要返回狀態值。
3>此外,一般的,寫插件的時候,在html中只寫基礎的元素結構,由jquery插件帶來的效果而產生的各種dom,均由插件本身生成(在jquery插件中產生)。
好了,放代碼:
jquery.flipswitch.js

;(function ($, window, document, undefined) { "use strict"; var pluginName = "flipswitch"; function Plugin(element, statusChanged) { this.element = element; this.statusChanged = statusChanged; this._name = pluginName; this.init(); } $.extend(Plugin.prototype, { init: function () { var base = this; var statusChanged = base.statusChanged; var $elem = $(base.element); $elem.wrap('<div class="' + $elem.attr("class") + '"></div>');//用DIV包裹$elem $("<span></span>").insertBefore($elem);//在$elem前插入 var $parent = $elem.parent(); if($elem.prop("checked")) {//prop()函數用於設置或返回當前jQuery對象所匹配的元素的屬性值。 $parent.addClass("active"); } $elem.on("change", function() { $parent.toggleClass("active"); if($.isFunction(statusChanged)) {//判斷是否是函數 statusChanged($elem.prop("checked")); } }) } }); $.fn[pluginName] = function (statusChanged) { return this.each(function () { if (!$.data(this, "plugin_" + pluginName)) { $.data(this, "plugin_" + pluginName, new Plugin(this, statusChanged)); } }); }; })(jQuery, window, document);
index.html

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>flipswitch</title> <style type="text/css"> .flipswitch { width: 48px; height: 28px; border-radius: 14px; cursor: pointer; background-color: #ccc; display: inline-block; text-align: left; position: relative; overflow: hidden; } .flipswitch > input[type=checkbox] { width: 100%; height: 100%; position: absolute; top: -10%; left: -5%; opacity: 0.01; cursor: pointer; } .flipswitch.active { text-align: right; background-color: #5cb85c; } .flipswitch span { width: 24px; height: 24px; margin: 2px; border-radius: 13px; display: inline-block; background: #fff; } </style> <script src="http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript" src="jquery.flipswitch.js"></script> <script type="text/javascript"> (function($, window, document) { $(function() { $(".flipswitch").flipswitch(function(status) { console.log(status); }); }) })(jQuery, window, document); </script> </head> <body> <div style="width: 800px; margin: 20px auto;"> <input type="checkbox" class="flipswitch" checked /> </div> </body> </html>
注意:在使用的時候,function(status){console.log(status)}將作為參數傳給插件中的statusChanged
例子2:自定義jQuery插件之在img上滑動產生幕布效果(來源於:https://github.com/wayou/sliphover/)
這種效果在產品,圖片展示的時候經常用到。
提示:
1.該插件有默認值,無返回值,注意代碼區別。
2.原理:鼠標移入時,鼠標移入到img區域的時候通過算法計算鼠標從哪個方向進來的(from),然后創建遮罩層並給遮罩層設置初始位置(left和bottom絕對定位),然后使用 animate方法逐漸移動到left:0,bottom:0(和img重合)的位置.鼠標移出img時,同樣的算法計算出移動到哪個方位(to),然后逐漸移出,並remove掉。
3.計算方向的算法比較神奇,大家可以研究下:

getDirection: function($target, event) {
//reference: http://stackoverflow.com/questions/3627042/jquery-animation-for-a-hover-with-mouse-direction
var w = $target.width(),
h = $target.height(),
x = (event.pageX - $target.offset().left - (w / 2)) * (w > h ? (h / w) : 1),
y = (event.pageY - $target.offset().top - (h / 2)) * (h > w ? (w / h) : 1),
direction = Math.round((((Math.atan2(y, x) * (180 / Math.PI)) + 180) / 90) + 3) % 4;
return direction;
}
好了,看看代碼吧:
jquery.sliphover.js

/** * sliphover v2.0.6 * require jquery 1.7+ * MIT License * for more info pls visit :https://github.com/wayou/SlipHover */ ; (function($, window, document, undefined) { // Create the defaults once var pluginName = "sliphover", defaults = { target: 'img', //the element that the overlay will attach to caption: 'title', //the caption that will display when hover duration: 'fast', //specify how long the animation will lasts in milliseconds fontColor: '#fff', textAlign: 'center', //display the caption left, center or right verticalMiddle: true, //display the caption vertical middle or not backgroundColor: 'rgba(0,0,0,.7)', //specify the background color and opacity using rgba backgroundColorAttr: null, //specify the attribute with background color value and opacity using rgba reverse: false, //reverse the direction height: '100%', //specify the height of the overlay withLink: true //if image is wraped with a link the overlay will be clickable, set false to disable click }; function SlipHover(element, options) { this.element = element; this.settings = $.extend({}, defaults, options); this._defaults = defaults; this._name = pluginName; this.version = 'v2.0.5'; this.init(); } SlipHover.prototype = { init: function() { //disable on touch devices if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { return; } var that = this, target = this.settings.target; //create the overlay container each time the mouse enters $(this.element).off('mouseenter', target).on('mouseenter', target, function(event) { //fix #9 https://github.com/wayou/SlipHover/issues/9 //use this instead of event.target for sometimes the event.target is not retriving the right target we want //http://stackoverflow.com/questions/9838137/event-target-jquery-on-mousedown-up-is-not-giving-the-dom-specified-by-selecto var $element = $(this), $overlayContainer = that.createContainer($element); $overlayContainer.off('mouseenter mouseleave').on('mouseenter mouseleave', function(event) { //if (!$overlayContainer) return; var direction = that.getDirection($(this), event); //check if reverse option is on direction = that.settings.reverse ? direction = (direction + 2) % 4 : direction; if (event.type === 'mouseenter') { //check if the previous overlay still exists before we create it var $overlay = $overlayContainer.find('.sliphover-overlay'); if (!$overlay.length) { $overlay = that.createOverlay(that, direction, $element); //put the overlay into the container $(this).html($overlay); } that.slideIn(that, $overlay);//滑入 } else { //slide out based on the direction that.removeOverlay(that, $(this), direction); } }); }); }, createContainer: function($element) { //get the properties of the target var top = $element.offset().top, left = $element.offset().left, //border = parseFloat($element.css("border-left-width")), width = $element.outerWidth(), height = $element.outerHeight(); zIndex = $element.css("z-index"); var $overlayContainer = $('<div>', { class: 'sliphover-container' }).css({ pointerEvent: 'none', width: width, height: height, position: 'absolute', overflow: 'hidden', top: top, left: left, borderRadius: $element.css('border-radius'), //in case the target has a round border, this will make the overlay more nicer zIndex: zIndex == +zIndex ? (zIndex + 1) : 999 // if the z-index of the target is not number then use 999 }); $('body').append($overlayContainer); return $overlayContainer; }, createOverlay: function(instance, direction, $element) { var bottom, left, $overlay, $overlayColor, content, $targetAParent; switch (direction) { case 0: //from top left = 0; bottom = '100%'; break; case 1: //from right left = '100%'; bottom = 0; break; case 2: //from bottom left = 0; bottom = '-100%'; break; case 3: //from left left = '-100%'; bottom = 0; break; default: window.console.error('error when get direction of the mouse'); }; //if we want to display content vertical middle, we need to wrap the content into a div and set the style with display table-cell if (instance.settings.verticalMiddle) { content = $('<div>').css({ display: 'table-cell', //此元素會作為一個表格單元格顯示(類似 <td> 和 <th>) verticalAlign: 'middle' }).html($element.attr(instance.settings.caption)); } else { content = $element.attr(instance.settings.caption); } $targetAParent = $element.parent('a'); if ($targetAParent.length && instance.settings.withLink) { var url = $targetAParent.attr('href'), target=$targetAParent.attr('target');//fix issue#17 $overlay = $('<a>', { class: 'sliphover-overlay', href: url || '#', target: target||'_self' }).css({ textDecoration: 'none' }); } else { $overlay = $('<div>', { class: 'sliphover-overlay' }); } $overlayColor = instance.settings.backgroundColorAttr ? $element.attr(instance.settings.backgroundColorAttr) : instance.settings.backgroundColor; $overlay.css({ width: '100%', height: instance.settings.height, position: 'absolute', left: left, bottom: bottom, display: instance.settings.verticalMiddle ? 'table' : 'inline', textAlign: instance.settings.textAlign, color: instance.settings.fontColor, backgroundColor: $overlayColor }).html(content); return $overlay; }, slideIn: function(instance, $overlay) { $overlay.stop().animate({ left: 0, bottom: 0 }, instance.settings.duration); }, removeOverlay: function(instance, $overlayContainer, direction) { var finalState, $overlay = $overlayContainer.find('.sliphover-overlay'); switch (direction) { case 0: //to top finalState = { bottom: '100%', left: 0 }; break; case 1: //to right finalState = { bottom: 0, left: '100%' }; break; case 2: //to bottom finalState = { bottom: '-100%', left: 0 }; break; case 3: //to left finalState = { bottom: 0, left: '-100%' }; break; default: window.console.error('error when get direction of the mouse'); }; //slide out $overlay.stop().animate(finalState, instance.settings.duration, function() { $overlayContainer.remove(); }); }, getDirection: function($target, event) { //reference: http://stackoverflow.com/questions/3627042/jquery-animation-for-a-hover-with-mouse-direction var w = $target.width(), h = $target.height(), x = (event.pageX - $target.offset().left - (w / 2)) * (w > h ? (h / w) : 1), y = (event.pageY - $target.offset().top - (h / 2)) * (h > w ? (w / h) : 1), direction = Math.round((((Math.atan2(y, x) * (180 / Math.PI)) + 180) / 90) + 3) % 4; return direction; } }; $.fn[pluginName] = function(options) { this.each(function() { if (!$.data(this, "plugin_" + pluginName)) { $.data(this, "plugin_" + pluginName, new SlipHover(this, options)); } }); // chain jQuery functions return this; }; })(jQuery, window, document);
index.html

<html> <head> <script src="http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script> <script src="jquery.sliphover.js"></script> </head> <body> <!-- demo begin --> <div class="demo" id="sliphover"> <ul> <li> <a href="#"><img src="img/1.jpg" title="this is a normal caption" /></a> </li> <li> <a href="#" > <img src="img/2.jpg" title="description goes here" /> </a> </li> <li> <a href="#"> <img src="img/3.jpg" title="with html, you can put <a href='#'>link</a> , <input type='button' value='button'> or any other element you want" /> </a> </li> <li> <a href="#"> <img src="img/4.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/5.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/6.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/7.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/8.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/9.jpg" title="description for this image" /> </a> </li> <li> <a href="#"> <img src="img/10.jpg" title="description for this image" /> </a> </li> </ul> </div> <!-- demo end --> <script type="text/javascript"> $(function() { //call sliphover plugin $('.demo').sliphover(); </script> </body> </html>
好的,就到這里了,jQuery插件的寫法就是這樣。
關於jQuery插件開發,還可以看一下官網:http://learn.jquery.com/plugins/