jquery插件一般是这么干的: $.fn.插件名称 = function(){}, 把插件的名称加在.fn上,在源码里面实际上是扩展到构造函数的原型对象上,如果你没看过jquery的源代码,或者你曾经看过,但是不知道为什么把插件扩展到fn上,那么本篇文章就能解答你的疑惑。关于jquery插件开发方式,可以参考我的这篇文章:
关于选项卡这个功能具体怎么做,不在这里详解,这个是入门级的功能,本文重在讨论插件开发的架构,扩展,以及参数设置。
如果你使用过jquery的选项卡插件,或者其他类型的插件,他们一般都是这么调用的:
$( ".tab" ).tabs( {} )
$(".tab").tabs( function(){} );
一种是传递参数定制插件行为
一种是传递函数定制插件行为
$(".tab") 选择到元素,然后返回的是jquery对象
tabs方法扩展在fn上就是扩展都jquery构造函数的原型对象上, 那么对象( $(".tab") )调用原型对象上的方法( tabs )当然就顺利成章了。
所以jquery插件扩展本质就是: 构造函数 + 原型对象扩展方法
定义一个构造+原型的方式,下面代码的原理,我在这篇文章有详细论述:
1 var G = function( selectors, context ){ 2 return new G.fn.init( selectors, context ); 3 } 4 G.fn = G.prototype = { 5 length : 0, 6 constructor : G, 7 size : function(){ 8 return this.length; 9 },10 init : function( selector, context ){11 this.length = 0;12 context = context || document;13 if ( selector.indexOf( '#' ) == 0 ){14 this[0] = document.getElementById( selector.substring( 1 ) );15 this.length = 1;16 }else {17 var aNode = context.querySelectorAll( selector );18 for( var i = 0, len = aNode.length; i < len; i++ ) {19 this[i] = aNode[i];20 }21 this.length = len;22 }23 this.selector = selector;24 this.context = context;25 return this;26 }27 }28 29 G.fn.init.prototype = G.fn;
接下来,我们还要添加一个插件扩展机制:
1 G.extend = G.fn.extend = function () { 2 var i = 1, 3 len = arguments.length, 4 dst = arguments[0], 5 j; 6 if (dst.length === undefined) { 7 dst.length = 0; 8 } 9 if (i == len) {10 dst = this;11 i--;12 }13 for (; i < len; i++) {14 for (j in arguments[i]) {15 dst[j] = arguments[i][j];16 dst.length++;17 }18 }19 return dst;20 };
在这篇文章: 有详细的论述,extend插件扩展机制
像使用jquery一样暴露接口:
var $ = function( selectors, context ){ return G( selectors, context ); } window.$ = $;
这个插件的扩展机制和元素选择机制就完成了,如果要扩展插件,只要在
G.fn上扩展插件的名称即可,如:
1 G.fn.tabs = function( options ){ 2 options = options || {}; 3 var defaults = { 4 contentClass : 'tab-content', 5 navClass : 'tab-nav', 6 activeClass : 'active', 7 triggerElements : '*', 8 activeIndex : 0, 9 evType : 'click',10 effect : 'none'11 };12 13 var opt = G.extend( {}, defaults, options );14 return this;15 }
这样,我们就在G的原型对象上扩展了一个tabs( 选项卡 )插件
options可以定制插件的行为:
contentClass : 'tab-content', 选项卡内容区域的class名称 navClass : 'tab-nav', 标签卡区域的class名称 activeClass : 'active', 标签卡默认选择的class名称:active triggerElements : '*', 标签卡默认触发元素 activeIndex : 0, 默认选中第几个标签卡 evType : 'click', 选项卡触发的事件类型 effect : 'none' 是否有过渡特效:如透明度 var opt = G.extend( {}, defaults, options );
这一段是把定制的配置和默认配置合成到一个对象opt里面,后面的插件行为,就可以根据opt的配置进行定制,这是插件开发参数定制中,常用的一招。
这样做的好处,可以防止污染默认配置defaults
1 var tabContent = this[0].querySelector( "." + opt.contentClass );2 var tabContentEle = tabContent.children;3 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements );4 5 var _contentLen = tabContentEle.length;6 var _index = opt.activeIndex;
获取对应的元素。
有了选项卡的元素和配置,我们就开始做业务处理(为所有选项卡添加处理的事件,进行选项卡切换)
定义一个专门的对象_api = {}, 扩展业务api
1 G.fn.tabs = function( options ){ 2 options = options || {}; 3 var defaults = { 4 contentClass : 'tab-content', 5 navClass : 'tab-nav', 6 activeClass : 'active', 7 triggerElements : '*', 8 activeIndex : 0, 9 evType : 'click',10 effect : 'none'11 };12 13 var opt = G.extend( {}, defaults, options );14 15 var tabContent = this[0].querySelector( "." + opt.contentClass );16 var tabContentEle = tabContent.children;17 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements );18 19 var _contentLen = tabContentEle.length;20 var _index = opt.activeIndex;21 22 var _api = {};23 24 _api.setIndex = function( index ){25 //当前标签加上active样式,其余标签删除active样式26 for ( var i = 0; i < _contentLen; i++ ) {27 if ( tabNavEle[i].classList.contains( 'active' ) ) {28 tabNavEle[i].classList.remove('active');29 }30 }31 tabNavEle[index].classList.add( 'active' );32 switch ( opt.effect ){33 case 'fade':34 break;35 default:36 for ( var i = 0; i < _contentLen; i++ ) {37 tabContentEle[i].style.display = 'none';38 }39 tabContentEle[index].style.display = 'block';40 _index = index;41 }42 }43 44 _api.setIndex( _index ); //默认的选项卡45 46 //所有的标签绑定事件47 for( var i = 0, len = tabNavEle.length; i < len; i++ ) {48 tabNavEle[i].index = i;49 tabNavEle[i].addEventListener( opt.evType, function(){50 var i = this.index;51 _api.setIndex( i );52 }, false );53 }54 55 return this;56 }
完整的插件代码:
1 /** 2 * Created by ghostwu(吴华). 3 */ 4 (function(){ 5 var G = function( selectors, context ){ 6 return new G.fn.init( selectors, context ); 7 } 8 G.fn = G.prototype = { 9 length : 0, 10 constructor : G, 11 size : function(){ 12 return this.length; 13 }, 14 init : function( selector, context ){ 15 this.length = 0; 16 context = context || document; 17 if ( selector.indexOf( '#' ) == 0 ){ 18 this[0] = document.getElementById( selector.substring( 1 ) ); 19 this.length = 1; 20 }else { 21 var aNode = context.querySelectorAll( selector ); 22 for( var i = 0, len = aNode.length; i < len; i++ ) { 23 this[i] = aNode[i]; 24 } 25 this.length = len; 26 } 27 this.selector = selector; 28 this.context = context; 29 return this; 30 } 31 } 32 33 G.fn.init.prototype = G.fn; 34 G.extend = G.fn.extend = function () { 35 var i = 1, 36 len = arguments.length, 37 dst = arguments[0], 38 j; 39 if (dst.length === undefined) { 40 dst.length = 0; 41 } 42 if (i == len) { 43 dst = this; 44 i--; 45 } 46 for (; i < len; i++) { 47 for (j in arguments[i]) { 48 dst[j] = arguments[i][j]; 49 dst.length++; 50 } 51 } 52 return dst; 53 }; 54 55 G.fn.tabs = function( options ){ 56 options = options || {}; 57 var defaults = { 58 contentClass : 'tab-content', 59 navClass : 'tab-nav', 60 activeClass : 'active', 61 triggerElements : '*', 62 activeIndex : 0, 63 evType : 'click', 64 effect : 'none' 65 }; 66 67 var opt = G.extend( {}, defaults, options ); 68 69 var tabContent = this[0].querySelector( "." + opt.contentClass ); 70 var tabContentEle = tabContent.children; 71 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements ); 72 73 var _contentLen = tabContentEle.length; 74 var _index = opt.activeIndex; 75 76 var _api = {}; 77 78 _api.setIndex = function( index ){ 79 //当前标签加上active样式,其余标签删除active样式 80 for ( var i = 0; i < _contentLen; i++ ) { 81 if ( tabNavEle[i].classList.contains( 'active' ) ) { 82 tabNavEle[i].classList.remove('active'); 83 } 84 } 85 tabNavEle[index].classList.add( 'active' ); 86 switch ( opt.effect ){ 87 case 'fade': 88 break; 89 default: 90 for ( var i = 0; i < _contentLen; i++ ) { 91 tabContentEle[i].style.display = 'none'; 92 } 93 tabContentEle[index].style.display = 'block'; 94 _index = index; 95 } 96 } 97 98 _api.setIndex( _index ); //默认的选项卡 99 100 //所有的标签绑定事件101 for( var i = 0, len = tabNavEle.length; i < len; i++ ) {102 tabNavEle[i].index = i;103 tabNavEle[i].addEventListener( opt.evType, function(){104 var i = this.index;105 _api.setIndex( i );106 }, false );107 }108 109 return this;110 }111 112 var $ = function( selectors, context ){113 return G( selectors, context );114 }115 window.$ = $;116 })();
选项卡布局:
1 2 3 4 5 6选项卡插件 - by ghostwu 7 8 9 16 17 18 34 35
选项卡插件样式:
1 * { 2 margin: 0; 3 padding: 0; 4 } 5 body { 6 font-size: 14px; 7 font-family: Tahoma, Verdana,"Microsoft Yahei"; 8 } 9 a{10 text-decoration: none;11 color:#000;12 }13 ul,li{14 list-style-type: none;15 }16 img {17 border:none;18 }19 .main {20 width:960px;21 margin:20px auto;22 }23 .tab{24 margin: 0 auto 20px;25 }26 .tab1 .tab-nav{27 margin-bottom:8px;28 }29 .tab .tab-nav {30 overflow:hidden;31 }32 .tab1 .tab-nav .active{33 border-bottom:1px solid #000;34 }35 .tab1 .tab-nav li {36 float:left;37 margin:0 10px;38 }39 .tab1 .tab-nav li a {40 line-height:40px;41 display:block;42 }43 .tab1 .tab-content {44 height:250px;45 overflow:hidden;46 }47 .tab1 .tab-content p {48 height:250px;49 background:#eee;50 }
最终效果: