jQuery弹簧插件编写基础之“又见弹窗”


Posted in Javascript onDecember 11, 2015

本文将通过一个实例来引出jQuery插件开发中的一些细节,首先介绍下jQuery插件开发的一些基础知识。

jQuery的插件开发主要分为两类:

1. 类级别,即在jQuery类本身上扩展方法,类似与 $.ajax,$.get 等。

2. 对象级别,这里所谓的对象是指通过jQuery选择器选中的jQuery对象,在该对象上添加方法。例如:$('div').css(), $('div').show() 等。

在实际开发中,我们通常选用对象级别的方法来开发插件,jQuery强大的选择器及对象操作是我们选择这种方式很大的一个原因。

接下来我们看看两种方式的具体写法是什么:

类级别的插件开发

$.extend({
  foo: function() {
  //...
  },
  bar: function() {
  
//...
  }
})
//调用
$.foo();

在这里,对扩展方法的命名需要考究一些,以免与jQuery类中的原有方法重名。即便如此,当我们需要在类上扩展多个方法时仍有可能会出现命名冲突的情况,为此我们可以创建自定义的命名空间:

$.myPlugin = {
  foo: function() {
    //...
  },
  bar: function() {
    //...
  }
}
 //调用
$.myPulgin.foo();

对象级别的插件开发

$.fn.foo = function() {
  //doSomething...
}
//调用(假设拥有一个id为obj的元素)
$('#obj').foo();
有个会问 fn 是什么东东?粘一段别人截取的jQuery源码就明白了:
jQuery.fn = jQuery.prototype = {
  init: function(selector, context) {
    //....
  }
}

原来是原型链啊。。。

接收配置参数

在编写一个插件时,我们可以让使用插件的人能按自己的意愿设置插件的一些属性,这就需要插件有接收参数的功能,同时当使用插件的人不传入参数时,插件内部也有一套自己默认的配置参数。

$.fn.foo = function(options) {
var defaults = {


color: '#000',

  backgroundColor: 'red'

};

var opts = $.extend({}, defaults, options); 

alert(opts.backgroundColor); //yellow
}
$('#obj').foo({
  backgroundColor: 'yellow'  
})

这里的关键就是 $.extend 方法,它能够将对象进行合并。对于相同的属性,后面的对象会覆盖前面的对象。为什么extend方法第一个参数是一个空对象呢?因为该方法会将后者合并到前者上,为了不让 defaults 被改变所以第一个参数设为空对象。

如果我们允许使用插件的人能够设置默认参数,就需要将其暴露出来:

$.fn.foo = function(options) {
var opts = $.extend({}, $.fn.foo.defaults, options); 

alert(opts.backgroundColor);
}
$.fn.foo.defaults = {
  color: '#000',
  backgroundColor: 'red'
}

这样就可以在外部对插件的默认参数进行修改了。

适当的暴露一些方法

$.fn.foo = function(options) {
var opts = $.extend({}, $.fn.foo.defaults, options); 

$.fn.foo.sayColor(opts.backgroundColor);
}
$.fn.foo.sayColor = function(bgColor) {
  alert(bgColor);
}
$.fn.foo.defaults = {
  color: '#000',
  backgroundColor: 'red'
}

改写:

$.fn.foo.sayColor = function(bgColor) {
  alert('background color is ' + bgColor);
}

暴露插件中的一部分方法是很牛逼的,它使得别人可以对你的方法进行扩展、覆盖。但是当别人对你的参数或方法进行修改时,很可能会影响其他很多东西。所以在考虑要不要暴露方法时候要头脑清楚,不确定的就不要暴露了。

保持函数的私有性

说到保持私有性,首先想到什么?没错,就是闭包:

;(function($) {
  $.fn.foo = function(options) {
    var opts = $.extend({}, $.fn.foo.defaults, options); 
    debug(opts.backgroundColor);
  }
  function debug(bgColors) {
    console.log(bgColors);
  }
  $.fn.foo.defaults = {
    color: '#000',
    backgroundColor: 'red'
  }
})(jQuery)

这是jQuery官方给出的插件开发方式,好处包括:1.没有全局依赖 2.避免其他人破坏 3.兼容 '$' 与 'jQuery' 操作符。

如上,debug 方法就成了插件内部的私有方法,外部无法对其进行修改。在闭包前面加 ; 是防止进行代码合并时,如果闭包前的代码缺少分号从而导致后面报错的情况。

合并

;(function($) {
  //定义插件
  $.fn.foo = function(options) {
    //doSomething...
  }
  //私有函数
  function debug() {
    //doSomething...
  }
  //定义暴露函数
  $.fn.foo.sayColor = function() {
    //doSomething...
  }
  //插件默认参数
  $.fn.foo.default = {
    color: '#000',
    backgroundColor: 'red'
  }
})(jQuery);

以上的代码就创建了一个完整且规范的插件骨架,看起来虽然很简单但在实际开发中还是有很多技巧与注意事项,接下来我们通过一个实例来看看。

想了半天,觉得将弹窗做成插件当作示例是比较合适的。在开发之前我们先构想一下这个弹窗插件的结构与功能等:

从上图我们看出包括三个部分,标题、内容、以及按钮组。这里需要申明一点,我们不想只做成浏览器里默认的只包含一个按钮的alert框,而是使用者可以自定义按钮数量,这样该弹出框也能完成类似confirm框的功能。

搭建插件骨架

function SubType($ele, options) {
  this.$ele = $ele;
  this.opts = $.extend({}, $.fn.popWin.defaults, options);
}
SubType.prototype = {
  createPopWin: function() {
  }
};
$.fn.popWin = function(options) {
  //this指向被jQuery选择器选中的对象
  var superType = new SubType(this, options);
  superType.createPopWin();
};
$.fn.popWin.defaults = {};

1. 我们创建了基于对象且名为 popWin 方法,并将 defaults 默认配置参数暴露出去以便使用的人进行修改;

2. 这里使用面向对象的方法来管理我们的私有函数,createPopWin 方法就是我们私有的用来创建弹窗的函数。

3. 在插件被调用时将jq对象与自定义的参数传入构造函数中并实例化。

调用

设想一下我们该怎么调用这个插件呢?我们可以在自己的文档树中合适的位置插入一个 div 元素,选中该 div 并调用我们定义在jQuery对象上的 popWin 方法。

$('#content').popWin({
  a: 1,
  b: 2,
  callback: function() {}
});

调用 popWin 的同时传入自定义的配置参数,之后被选中的 div 元素就被神奇的转化成一个弹窗了!当然,这只是我们的设想,下面开始码代码。

确定默认配置

$.fn.popWin.defaults = {
  width: '600', //弹窗宽
  height: '250', //弹窗高
  title: '标题', //标题
  desc: '描述', //描述
  winCssName: 'pop-win', //弹窗的CSS类名
  titleCssName: 'pop-title', //标题区域的CSS类名
  descCssName: 'pop-desc', //描述区域的CSS类名
  btnAreaCssName: 'pop-btn-box', //按钮区域的CSS类名
  btnCssName: 'pop-btn', //单个按钮的CSS类名
  btnArr: ['确定'], //按钮组
  callback: function(){} //点击按钮之后的回调函数
}

我们定义了如上的参数,为什么有要传入这么多的CSS类名呢?1. 为了保证JS与CSS尽可能的解耦。 2. 你的样式有很大可能别人并不适用。所以你需要配置一份样式表文件来对应你的默认类名,当别人需要更改样式时可以传入自己编写的样式。

按钮组为一个数组,我们的弹窗需要根据其传入的数组长度来动态的生成若干个按钮。回调函数的作用是在用户点击了某个按钮时返回他所点击按钮的索引值,方便他进行后续的操作。

弹窗DOM创建

var popWinDom,titleAreaDom,descAreaDom,btnAreaDom;
SubType.prototype = {
createPopWin: function() {


var _this = this;
    //首次创建弹窗


//背景填充整个窗口


this.$ele.css({



position: 'fixed',



top: 0,



left: 0,



right: 0,



bottom: 0,



backgroundColor: 'rgba(0,0,0,0.4)',



overflow: 'hidden'


});





//窗口区域


popWinDom = $('<div><div></div><div></div><div></div></div>').css({



width: this.opts.width,



height: this.opts.height,



position: 'absolute',



top: '30%',



left: '50%',



marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px'


}).attr('class',this.opts.winCssName);





//标题区域


titleAreaDom = popWinDom.find('div:eq(0)')







.text(this.opts.title)







.attr('class',this.opts.titleCssName);





//描述区域


descAreaDom = popWinDom.find('div:eq(1)')
            .text(this.opts.desc)
            .attr('class',this.opts.descCssName);


//按钮区域
 


btnAreaDom = popWinDom.find('div:eq(2)')
            .attr('class',this.opts.btnAreaCssName);





//插入按钮


this.opts.btnArr.map(function(item, index) {



btnAreaDom.append($('<button></button>')




.text(item)




.attr({'data-index':index, 'class':_this.opts.btnCssName})




.on('click', function() {





_this.opts.callback($(this).attr('data-index'));




}));



});





this.$ele.append(popWinDom);
  }
}

1. 首先命名了四个变量用来缓存我们将要创建的四个DOM,将传入的jQuery对象变形成覆盖整个窗口半透明元素;

2. 创建窗口DOM,根据传入的高、宽来设置尺寸并居中,之后另上传入的窗口CSS类名;

3. 创建标题、描述、按钮组区域,并将传入的标题、描述内容配置上去;

4. 动态加入按钮,并为按钮加上data-index的索引值。注册点击事件,点击后调用传入的回调函数,将索引值传回。

好了,我们先看下效果。调用如下:

$('#content').popWin({
  width: '500',
  height: '200',
  title: '系统提示',
  desc: '注册成功',
  btnArr: ['关闭'],
  callback: function(clickIndex) {
    console.log(clickIndex);
  }
});

可以看到一个弹窗的DOM已被渲染到页面中了,当点击关闭按钮时控制台会打印出 "0",因为按钮组只有一个值嘛,当然是第0个了。

如果我们需要多次调用这个弹窗,每次都要传入高、宽我会觉得很麻烦。这时我们可以直接在一开始修改插件内部的默认配置,这也是我们将默认配置暴露的好处:

$.fn.popWin.defaults.width = '500';
$.fn.popWin.defaults.height = '200';

要注意的当然是不能直接改变defaults的引用,以免露掉必须的参数。 这样以后的调用都无需传入尺寸了。

我们加一个按钮并且传入一个自定义的样式看看好使不呢?

$('#content').popWin({
  title: '系统提示',
  desc: '是否删除当前内容',
  btnArr: ['确定','取消'],
  winCssName: 'pop-win-red',
  callback: function(clickIndex) {
    console.log(clickIndex);
  }
});

可以看到都是生效了的,当点击“确定”按钮时回调函数返回 0,点击“取消”按钮时回调函数返回 1。这样使用插件的人就知道自己点击的是哪一个按钮,以完成接下来的操作。

显示&隐藏

接下来要进行打开、关闭弹窗功能的开发。回想上面介绍的概念,我们想让使用该插件的人能够对这两个方法进行扩展或者重写,所以将这两个方法暴露出去:

$.fn.popWin.show = function($ele) {
  $ele.show();
}
$.fn.popWin.hide = function($ele) {
  $ele.hide();
}

之后在createPopWin方法中需要的地方调用这两个方法。

这里多强调一点,也是做弹窗控件不可避免的一点:只有当我们点击按钮以及灰色背景区域时允许弹窗关闭,点击弹窗其他地方不允许关闭。由于弹窗属于整个灰色区域的子节点,必然牵扯到的就是事件冒泡的问题。

所以在给最外层加上点击关闭的事件时,要在弹窗区域阻止事件冒泡。

popWinDom = $('<div><div></div><div></div><div></div></div>').css({
  width: this.opts.width,
  height: this.opts.height,
  position: 'absolute',
  top: '30%',
  left: '50%',
  marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px'
}).attr('class',this.opts.winCssName).on('click', function(event) {
event.stopPropagation();
});

二次打开

我们只需要在第一次调用插件时创建所有创建DOM,第二次调用时只更改其参数即可,所以在createPopWin方法最前面加入如下方法:

if (popWinDom) { //弹窗已创建
popWinDom.css({


width: this.opts.width,


height: this.opts.height

}).attr('class',this.opts.winCssName);

titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName);

descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName);

btnAreaDom.html('').attr('class',this.opts.btnAreaCssName);

this.opts.btnArr.map(function(item, index) {


btnAreaDom.append($('<button></button>')



.text(item)



.attr('data-index',index)



.attr('class',_this.opts.btnCssName)



.on('click', function() {




_this.opts.callback($(this).attr('data-index'));




$.fn.popWin.hide(_this.$ele);



}));


});

$.fn.popWin.show(this.$ele);

return;
}

合并整个插件代码

;(function($) {
function SubType(ele, options) {


this.$ele = ele;


this.opts = $.extend({}, $.fn.popWin.defaults, options);

}

var popWinDom,titleAreaDom,descAreaDom,btnAreaDom;

SubType.prototype = {


createPopWin: function() {



var _this = this;



if (popWinDom) { //弹窗已创建




popWinDom.css({





width: this.opts.width,





height: this.opts.height




}).attr('class',this.opts.winCssName);




titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName);




descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName);




btnAreaDom.html('').attr('class',this.opts.btnAreaCssName);




this.opts.btnArr.map(function(item, index) {





btnAreaDom.append($('<button></button>')






.text(item)






.attr('data-index',index)






.attr('class',_this.opts.btnCssName)






.on('click', function() {







_this.opts.callback($(this).attr('data-index'));







$.fn.popWin.hide(_this.$ele);






}));




});




$.fn.popWin.show(this.$ele);




return;
      }
      //首次创建弹窗
      this.$ele.css({
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(0,0,0,0.4)',
        overflow: 'hidden',
        display: 'none'
      }).on('click', function() {
        $.fn.popWin.hide(_this.$ele);
      });
      popWinDom = $('<div><div></div><div></div><div></div></div>').css({
        width: this.opts.width,
        height: this.opts.height,
        position: 'absolute',
        top: '30%',
        left: '50%',
        marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px'
      }).attr('class',this.opts.winCssName).on('click', function(event) {
        event.stopPropagation();
      });
      titleAreaDom = popWinDom.find('div:eq(0)')
              .text(this.opts.title)
              .attr('class',this.opts.titleCssName);
      descAreaDom = popWinDom.find('div:eq(1)')
              .text(this.opts.desc)
              .attr('class',this.opts.descCssName);
      btnAreaDom = popWinDom.find('div:eq(2)')
              .attr('class',this.opts.btnAreaCssName);
      this.opts.btnArr.map(function(item, index) {
        btnAreaDom.append($('<button></button>')
          .text(item)
          .attr({'data-index':index, 'class':_this.opts.btnCssName})
          .on('click', function() {
            _this.opts.callback($(this).attr('data-index'));
            $.fn.popWin.hide(_this.$ele);
          }));
      });
      this.$ele.append(popWinDom);
      $.fn.popWin.show(this.$ele);
    }
  }
  $.fn.popWin = function(options) {
    var superType = new SubType(this, options);
    superType.createPopWin();
    return this;
  }
  $.fn.popWin.show = function($ele) {
    $ele.show();
  }
  $.fn.popWin.hide = function($ele) {
    $ele.hide();
  }
  $.fn.popWin.defaults = {
    width: '600',
    height: '250',
    title: 'title',
    desc: 'description',
    winCssName: 'pop-win',
    titleCssName: 'pop-title',
    descCssName: 'pop-desc',
    btnAreaCssName: 'pop-btn-box',
    btnCssName: 'pop-btn',
    btnArr: ['确定'],
    callback: function(){}
  }
})(jQuery);

如上,一个完整的弹窗插件就在这里了。

说下这个标红的 return this 是干什么用的,前面已说过 this 在这里是被选中的jQuery对象。将其return就可以在调用完我们的插件方法后可以继续调用jQ对象上的其他方法,也就是jQuery的链式操作,说玄乎点就叫级联函数。

OK!趁热打铁,我们来看看暴露出去的两个方法重写之后效果怎么样,毕竟对插件暴露部分的扩展和重写是很牛逼的一块东西。

想象个情景,你用了这个插件后觉得简单的show和hide效果简直是low爆了,决定重写这个弹出和隐藏的效果:

$.fn.popWin.show = function($ele) {
$ele.children().first().css('top','-30%').animate({top:'30%'},500);

$ele.show();
}
$.fn.popWin.hide = function($ele) {

$ele.children().first().animate({top:'-30%'},500,function() {


$ele.hide();

});
}

你在自己的代码里加上上面两段,然后发现弹窗有了一个简单的上下滑动进入屏幕的效果,同时又不会影响我们弹窗的创建,证明我们的暴露方法还算合理。

当然你也可以让它竖着进、横着进、翻着跟头进,这就看你自己了。

最后贴上默认的样式表,为了急着想粘回去试试的同学们。

.pop-win {
  border: 1px solid #fff;
  padding: 10px;
  background-color: #fff;
  -wekbit-border-radius: 6px;
  border-radius: 6px;
  -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3);
  box-shadow: 0 3px 9px rgba(0,0,0,0.3);
}
.pop-win-red {
  padding: 10px;
  background-color: red;
  -wekbit-border-radius: 6px;
  border-radius: 6px;
  -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3);
  box-shadow: 0 3px 9px rgba(0,0,0,0.3);
}
.pop-title {
  width: 100%;
  height: 20%;
  line-height: 40px;
  padding-left: 10px;
  box-sizing: border-box;
  border-bottom: 1px solid #eee;
  font-size: 17px;
  font-weight: bold;
}
.pop-desc {
  width: 100%;
  height: 60%;
  box-sizing: border-box;
  padding: 10px 0 0 10px;
  border-bottom: 1px solid #eee;
}
.pop-btn-box {
  width: 100%;
  height: 20%;
  text-align: right;
}
.pop-btn {
  margin: 10px 10px 0 0;
  width: 60px;
  height: 30px;
}

当然这只是个编写插件的例子,如果要拿出去使用还需要仔细打磨。例子虽然简单,旨在抛砖引玉。

Javascript 相关文章推荐
JS实现点击下载的小例子
Jul 10 Javascript
JavaScript闭包函数访问外部变量的方法
Aug 27 Javascript
jQuery解析XML与传统JavaScript方法的差别实例分析
Mar 05 Javascript
js鼠标点击按钮切换图片-图片自动切换-点击左右按钮切换特效代码
Sep 02 Javascript
8 行 Node.js 代码实现代理服务器
Dec 05 Javascript
View.post() 不靠谱的地方你知道多少
Aug 29 Javascript
Javascript中将变量转换为字符串的三种方法
Sep 19 Javascript
浅谈Angular 中何时取消订阅
Nov 22 Javascript
微信小程序使用map组件实现解析经纬度功能示例
Jan 22 Javascript
微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)
Jan 24 Javascript
Vue调用后端java接口的实例代码
Oct 28 Javascript
vue @ ~ 相对路径 路径别名设置方式
Jun 05 Vue.js
jQuery点击改变class并toggle及toggleClass()方法定义用法
Dec 11 #Javascript
Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(二)
Dec 10 #Javascript
Express的路由详解
Dec 10 #Javascript
在 Express 中使用模板引擎
Dec 10 #Javascript
Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(一)
Dec 10 #Javascript
基于jQuery实现复选框是否选中进行答题提示
Dec 10 #Javascript
日常收集整理的JavaScript常用函数方法
Dec 10 #Javascript
You might like
PHP循环语句笔记(foreach,list)
2011/11/29 PHP
如何把php5.3版本升级到php5.4或者php5.5
2015/07/31 PHP
php使用pear_smtp发送邮件
2016/04/15 PHP
laravel 解决groupBy时出现的错误 isn't in Group By问题
2019/10/17 PHP
window.ActiveXObject使用说明
2010/11/08 Javascript
AJAX分页的代码(后台asp.net)
2011/02/14 Javascript
JS实现标签页效果(配合css)
2013/04/03 Javascript
逐一介绍Jquery data()、Jquery stop()、jquery delay()函数(详)
2015/11/04 Javascript
异步加载JS、CSS代码(推荐)
2016/06/15 Javascript
第一次接触神奇的Bootstrap表单
2016/07/27 Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
2016/09/23 Javascript
基于JQuery及AJAX实现名人名言随机生成器
2017/02/10 Javascript
JS中正则表达式全局匹配模式 /g用法详解
2017/04/01 Javascript
js中less常用的方法小结
2017/08/09 Javascript
layui实现table加载的示例代码
2018/08/14 Javascript
微信小程序实现用table显示数据库反馈的多条数据功能示例
2019/05/07 Javascript
Vue事件处理原理及过程详解
2020/03/11 Javascript
Python中的迭代器漫谈
2015/02/03 Python
python使用三角迭代计算圆周率PI的方法
2015/03/20 Python
修复CentOS7升级Python到3.6版本后yum不能正确使用的解决方法
2018/01/26 Python
关于python列表增加元素的三种操作方法
2018/08/22 Python
在pycharm上mongodb配置及可视化设置方法
2018/11/30 Python
基于python实现的百度音乐下载器python pyqt改进版(附代码)
2019/08/05 Python
在python中使用nohup命令说明
2020/04/16 Python
Python faker生成器生成虚拟数据代码实例
2020/07/20 Python
python实现一个简单RPC框架的示例
2020/10/28 Python
Python实现图片指定位置加图片水印(附Pyinstaller打包exe)
2021/03/04 Python
css3 pointer-events 介绍详解
2017/09/18 HTML / CSS
猎人靴英国官网:Hunter Boots
2017/02/02 全球购物
高三生物教学反思
2014/01/25 职场文书
会计学自我鉴定
2014/02/06 职场文书
商业融资计划书
2014/04/29 职场文书
护理见习报告范文
2014/11/03 职场文书
个人专业技术总结
2015/03/05 职场文书
2016高考感言
2015/08/01 职场文书
Requests什么的通通爬不了的Python超强反爬虫方案!
2021/05/20 Python