首頁 web前端 js教程 jQuery彈簧插件編寫基礎之「又見彈跳窗」_jquery

jQuery彈簧插件編寫基礎之「又見彈跳窗」_jquery

May 16, 2016 pm 03:26 PM

本文將透過一個實例來引出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;
}
登入後複製

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

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

JavaScript難以學習嗎? JavaScript難以學習嗎? Apr 03, 2025 am 12:20 AM

學習JavaScript不難,但有挑戰。 1)理解基礎概念如變量、數據類型、函數等。 2)掌握異步編程,通過事件循環實現。 3)使用DOM操作和Promise處理異步請求。 4)避免常見錯誤,使用調試技巧。 5)優化性能,遵循最佳實踐。

如何實現視差滾動和元素動畫效果,像資生堂官網那樣?
或者:
怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? 如何實現視差滾動和元素動畫效果,像資生堂官網那樣? 或者: 怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? Apr 04, 2025 pm 05:36 PM

實現視差滾動和元素動畫效果的探討本文將探討如何實現類似資生堂官網(https://www.shiseido.co.jp/sb/wonderland/)中�...

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

See all articles