jQuery插件开发之实例讲解
在我们日常开发工作中,当我们需要某种界面效果的时候,一般会到网上搜索一些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插件的写法就是这样。