JQuery插件的開發(fā)真的有那么難嗎
【前言】
jQuery已經(jīng)被廣泛使用,憑借其簡潔的API,對DOM強大的操控性,易擴展性越來越受到web開發(fā)人員的喜愛,我在社區(qū)也發(fā)布了很多的jQuery插件,經(jīng)常有人詢問一些技巧,因此干脆寫這么一篇文章給各位jQuery愛好者,算是拋磚引玉吧。
【基礎】
a)樣式
很多人會認為樣式是個很復雜的東西,需要沉著冷靜的心態(tài)加上非凡的審美觀才能設計出賞心悅目的UI,拋開圖片設計不說,其實css也就是那么些屬性:position,margin,padding,width,height,left,top,float,border,background...
UI設計的漂亮與否在很大程度上依賴于設計人員對配色的把握和整體效果的協(xié)調(diào)。舉個簡單的例子,一個簡單的頁面,馬虎的人:
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Test Pagetitle>
- head>
- <body>
- jQuery是一個框架!壓縮后有30多k吧。
- body>
- html>
細心的人:
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Test Pagetitle>
- <style type="text/css">
- body
- {
- font-family:'宋體';
- font-size:12px;
- }
- style>
- head>
- <body>
- jQuery是一個框架!壓縮后有30多k吧。
- body>
- html>
專心的人:
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Test Pagetitle>
- <style type="text/css">
- body
- {
- font-family:'Verdana','宋體';
- font-size:12px;
- }
- style>
- head>
- <body>
- jQuery是一個框架!壓縮后有30多k吧。
- body>
- html>
我們對比一下三者的UI效果:
一目了然,或許很多的站點失去關注正是因為這不起眼的font-family,font-size。當然這還只是個簡單的例子,掌握css應該從簡單做起,從基本入手,在實踐中運用并不斷深入。
b)腳本
我們同樣需要對javascript有著深刻的理解,對dom, xhr, Regex, call-apply, prototype等都應該有一定的了解。
有人會說要這些有啥用啊,對dom的操作其實通過getElementById, getElementsByTagName以及其他的API都可以輕松的完成,這話是沒錯,當思路確定后,思想才是重點,一段代碼是精華還是糟粕很容易就可以區(qū)分出來,究其原因還是取決你自己,舉個簡單的例子,大量的html組裝,
路人甲:
- var a = new Array(10);
- var menu = '';
- for (var i = 0; i < a.length; i++) {
- menu += '' + a[i] + '" >' + a[i] + '';
- }
路人乙:
- String.prototype.format = function() {
- var args = arguments;
- return this.replace(/{(\d{1})}/g, function() {
- return args[arguments[1]];
- });
- };
- var a = new Array(1,2,3,4,5,6,7,8,9,0);
- var m = '{0}';
- for (var i = 0; i < a.length; i++) {
- menu += m.format(a[i]);
- }
在實現(xiàn)方式明確的情況下,優(yōu)雅高效的代碼顯然更具吸引力。
【實踐】
jQuery開發(fā)或使用,更多的靈感是來自實踐,而不是copy||paste(奉行拿來主義的同學可以離開了)。
那么在這里我會用一個簡單的例子來闡述jQuery插件開發(fā)的流程,能否舉一反三就看各位看官了。
【目的】
開發(fā)一個插件之前我們需要對自己的目的有一個清醒的認識,有很明確的方向感,那么此次我作為示例插件的目的,就是呈現(xiàn)一個用于UI的Slider - 滑動條,常年從事于或暫時專注于win32開發(fā)的同學應該比較了解。
草圖
真正動手編碼之前我們還需要有一個草圖來描述自己插件的“長相”(事件驅(qū)動或API封裝的可以忽略)。
很多的同學在做UI開發(fā)前往往會忙于搜集各種小圖片(非精通ps或iconworkshop人士),其實漂亮的圖標的確可以美化我們的UI,不過我一般的處理方式是編寫易于擴展的css,前期的UI呈現(xiàn)盡量少使用圖片,多用線條完成。
ok,言歸正卷,那么我的slider設計草圖是:
解釋下下文將用到的幾個詞:
slider: 此部分是作為拖拽手柄來使用,用戶可以通過拖拽此部分來更新completed bar的位置。
completed: 此部分作為bar的內(nèi)嵌元素,作為特殊效果來顯示slider與起始點的距離,亦即與slider的value值關聯(lián)。
bar: slider的載體,completed的滿值。
思路:
slider作為手柄提供拖拽功能,作用區(qū)域為bar,拖拽過程中completed條必須實時更新(長度),影響區(qū)域為slider至bar左端的距離。
【編碼】
開發(fā)jQuery UI/Effect 插件在很多時候都需要與UI交互,因此在呈現(xiàn)上需要提供Html tree來繪制我們的插件,最終通過js dom來輸出,那么在繪制簡單的dom結(jié)構(gòu)的時候我會直接用js來完成,不過如果嵌套比較復雜的話,我們還是應該先用html來完成,然后轉(zhuǎn)變成js輸出。
html tree:
- <div class="defaultbar">
- <div class="jquery-completed"> div>
- <div class="jquery-jslider"> div>
- div>
deafultbar -> bar
jquery-completed -> completed
jquery-jslider -> slider
前期UI呈現(xiàn)上我們不使用圖片,盡量用線條、顏色來完成:
- /*----default skin----*/
- .defaultbar
- {
- margin-top: 10px;
- height: 5px;
- background-color: #FFFFE0;
- border: 1px solid #A9C9E2;
- position: relative;
- }
- .defaultbar .jquery-completed
- {
- height: 3px;
- background-color: #7d9edb;
- top: 1px;
- left:1px;
- position: absolute;
- }
- .defaultbar .jquery-jslider
- {
- height: 15px;
- background-color: #E6E6FA;
- border: 1px solid #A5B6C8;
- top: -6px;
- display: block;
- cursor: pointer;
- position: absolute;
- }
將bar的position屬性設置成relative,以方便子節(jié)點的浮動(子節(jié)點使用position:absolute來獲得內(nèi)聯(lián)浮動效果)。
那么我們可以看下這個css和html tree產(chǎn)生的UI效果:
ok,具備了所需的元素 - slider, completed, bar.
一些規(guī)范:
當我們畫出了UI之后就可以正式編寫jQuery插件代碼了,不過在著之前我們還需要對jQuery插件開發(fā)的一些規(guī)范性有一些了解。
1. 使用閉包:
- (function($) {
- // Code goes here
- })(jQuery);
這是來自jQuery官方的插件開發(fā)規(guī)范要求,使用這種編寫方式有什么好處呢?
a) 避免全局依賴。
b) 避免第三方破壞。
c) 兼容jQuery操作符'$'和'jQuery '
我們知道這段代碼在被解析時會形同如下代碼:
- var jq = function($) {
- // Code goes here
- };
- jq(jQuery);
這樣效果就一目了然了。
2. 擴展
jQuery提供了2個供用戶擴展的‘基類’ - $.extend和$.fn.extend.
$.extend 用于擴展自身方法,如$.ajax, $.getJSON等,$.fn.extend則是用于擴展jQuery類,包括方法和對jQuery對象的操作。為了保持jQuery的完整性,我比較趨向于使用$.fn.extend進行插件開發(fā)而盡量少使用$.extend.
3. 選擇器
jQuery提供了功能強大,并兼容多種css版本的選擇器,不過發(fā)現(xiàn)很多同學在使用選擇器時并未注重效率的問題。
a) 盡量使用Id選擇器,jQuery的選擇器使用的API都是基于getElementById或getElementsByTagName,因此可以知道效率***的是Id選擇器,因為jQuery會直接調(diào)用getElementById去獲取dom,而通過樣式選擇器獲取jQuery對象時往往會使用 getElementsByTagName去獲取然后篩選。
b) 樣式選擇器應該盡量明確指定tagName, 如果開發(fā)人員使用樣式選擇器來獲取dom,且這些dom屬于同一類型,例如獲取所有className為jquery的div,那么我們應該使用的寫法是$('div.jquery')而不是$('.jquery'),這樣寫的好處非常明顯,在獲取dom時jQuery會獲取div然后進行篩選,而不是獲取所有dom再篩選。
c) 避免迭代,很多同學在使用jQuery獲取指定上下文中的dom時喜歡使用迭代方式,如$('.jquery .child'),獲取className為jquery的dom下的所有className為child的節(jié)點,其實這樣編寫代碼付出的代價是非常大的,jQuery會不斷的進行深層遍歷來獲取需要的元素,即使確實需要,我們也應該使用諸如$(selector,context), $('selector1>selector2'), $(selector1).children(selector2), $(selctor1).find(selector2)之類的方式。
開始編碼
話題有點扯遠,ok,在對UI有了清晰的認識后我們就可以使用js來輸出html了。
我們使用jSlider來命名這個slider插件(為了避免插件沖突,插件命名時也應十分考究,這里我就俗一回)。
- $.extend($.fn, {
- ///
- /// apply a slider UI
- ///
- jSlider: function(setting) {
- }
- });
在插件開發(fā)中比較標準的方式是將元數(shù)據(jù)獨立出來并開放API,比如這里的setting參數(shù)傳入值,有時候為了減少代碼編寫量,我習慣于直接在插件內(nèi)賦值:
- var ps = $.extend({
- renderTo: $(document.body),
- enable: true,
- initPosition: 'max',
- size: { barWidth: 200, sliderWidth: 5 },
- barCssName: 'defaultbar',
- completedCssName: 'jquery-completed',
- sliderCssName: 'jquery-jslider',
- sliderHover: 'jquery-jslider-hover',
- onChanging: function() { },
- onChanged: function() { }
- }, setting);
規(guī)范的做法:
- $.fn.jSlider.default = {
- renderTo: $(document.body),
- enable: true,
- initPosition: 'max',
- size: { barWidth: 200, sliderWidth: 5 },
- barCssName: 'defaultbar',
- completedCssName: 'jquery-completed',
- sliderCssName: 'jquery-jslider',
- sliderHover: 'jquery-jslider-hover',
- onChanging: function() { },
- onChanged: function() { }
- };
- $.extend({},$.fn.jSlider.default,setting);
ok, 下面描述下我所定義的這些API的作用:
renderTo: jSlider的載體、容器,可以是一個jQuery對象,也可以是選擇器。
enable: jSlider插件是否可用,true時end-user可拖拽,否則禁止。
initPosition: jSlider的初始值,‘max’或者‘min’,亦即 slider的value值,1或者0。
size: jSlider的參數(shù),包括2個值barWidth - bar的長度, sliderWidth - slider的長度。
barCssName: bar的樣式名稱,便于end-user自行擴展樣式。
completedCssName: completed的樣式名稱。
sliderCssName: slider的樣式名稱。
sliderHover: slider聚焦時的樣式名稱。
onChanging: slider被拖拽時觸發(fā)的事件。
onChanged: slider拖拽結(jié)束時觸發(fā)的事件。
此時我們需要將renderTo強制轉(zhuǎn)換成jQuery對象(兼容使用selector的情況):
- ps.renderTo = (typeof ps.renderTo == 'string' ?
- $(ps.renderTo) : ps.renderTo);
然后將html tree輸出到render:
- /* ---------->
- html tree:
- ---->sliderbar
- ----> completed bar
- ----> slider
- <-----------*/
- var sliderbar = $('')
- .attr('class', ps.barCssName)
- .css('width', ps.size.barWidth)
- .appendTo(ps.renderTo);
- var completedbar = sliderbar.find('div:eq(0)')
- .attr('class', ps.completedCssName);
- var slider = sliderbar.find('div:eq(1)')
- .attr('class', ps.sliderCssName)
- .css('width', ps.size.sliderWidth);
這樣我們就在UI上直接呈現(xiàn)了Html并且用定制的css進行渲染,分別用sliderbar, completedbar, slider對我們需要的三個對象進行緩存。
ok, 在呈現(xiàn)了UI后我們就需要提供方法來實現(xiàn)slider的拖拽,在這之前我們還需要實現(xiàn)一個方法,就是completedbar的實時更新,即在拖動slider的時候讓completedbar始終填充左側(cè)區(qū)域:
- var bw = sliderbar.width(), sw = slider.width();
- //make sure that the slider was displayed in the bar(make a limited)
- ps.limited = { min: 0, max: bw - sw };
- if (typeof window.$sliderProcess == 'undefined') {
- window.$sliderProcess = new Function('obj1', 'obj2', 'left',
- 'obj1.css(\'left\',left);obj2.css(\'width\',left);');
- }
- $sliderProcess(slider, completedbar, eval('ps.limited.' + ps.initPosition));
bw,sw用來存儲sliderbar和slider的長度,此處沒有直接使用ps.size里的值是為了防止樣式里的border-width對width造成破壞。
定義一個私用成員limited來存儲slider[left]的***值和最小值,并在后面直接使用eval('ps.limited.' + ps.initPosition)來獲取,從而避免switch操作。
同時還需定義一個全局Function用來定位completedbar的填充長度以及slider左側(cè)距離,我給其命名為$sliderProcess。
那么我們接下來剩下的工作就是slider的拖拽功能了,那么在這里我會用到之前發(fā)布的一款jQuery拖拽插件,并做適量的訂制:
- //drag and drop
- var slide = {
- drag: function(e) {
- var d = e.data;
- var l = Math.min(Math.max(e.pageX - d.pageX + d.left, ps.limited.min), ps.limited.max);
- $sliderProcess(slider, completedbar, l);
- //push two parameters: 1st:percentage, 2nd: event
- ps.onChanging(l / ps.limited.max, e);
- },
- drop: function(e) {
- slider.removeClass(ps.sliderHover);
- //push two parameters: 1st:percentage, 2nd: event
- ps.onChanged(parseInt(slider.css('left')) / ps.limited.max, e);
- $().unbind('mousemove', slide.drag).unbind('mouseup', slide.drop);
- }
- };
- if (ps.enable) {
- //bind events
- slider.bind('mousedown', function(e) {
- var d = {
- left: parseInt(slider.css('left')),
- pageX: e.pageX
- };
- $(this).addClass(ps.sliderHover);
- $().bind('mousemove', d, slide.drag).bind('mouseup', d, slide.drop);
- });
- }
這樣當jSlider enable屬性為true時,在end-user按下鼠標時綁定mousemove事件,在鼠標彈起時移除,我們只需要同步更新slider的left 屬性和completedbar的width即可,同時在drag中綁定onChanging方法,在drop中綁定onChanged方法,向這兩個方法推送的參數(shù)相同,1>百分比,即value值,介于0~1,2>event。
那么至此我們的jSlider插件就基本成型,向用戶提供了一個可拖拽的slider。
【擴展】
有的時候用戶卻不是那么容易滿足,于是有人高呼:“我要自己設置value,為什么不提供這個功能?”。
那么這時我們就需要為用戶公開一個方法,用于設置jSlider的value,首先考慮的是作為方法需要一個作用對象(jSlider),那么此時我又不想將作用對象作為參數(shù)傳入,那么我們還是將這個方法作為插件來開發(fā),我們將方法命名為setSliderValue,開放2個參數(shù),v(value值)和 callback(設置完成后的回調(diào)函數(shù))。
即:$.fn.setSliderValue(v,callback);
ok,那么剩下的就是作用對象了,由之前的設計可知,在slider拖動時主要作用于2個對象,slider和completedbar,那么我們在jSlider插件末尾加上一段代碼來返回slider對象:
- slider.data = { bar: sliderbar, completed: completedbar };
- return slider;
這樣我們在初始化jSlider的時候就可以直接用一個變量來獲取jSlider對象,然后調(diào)用setSliderValue方法了,偽碼:
- var slider = $.fn.jSlider({});
- slider.setSliderValue(v,function(){});
setSliderValue代碼:
- try {
- //validate
- if (typeof v == 'undefined' || v < 0 || v > 1) {
- throw new Error('\'v\' must be a Float variable between 0 and 1.');
- }
- var s = this;
- //validate
- if (typeof s == 'undefined' ||
- typeof s.data == 'undefined' ||
- typeof s.data.bar == 'undefined') {
- throw new Error('You bound the method to an object that is not a slider!');
- }
- $sliderProcess(s, s.data.completed, v * s.data.bar.width());
- if (typeof callback != 'undefined') { callback(v); }
- }
- catch (e) {
- alert(e.message);
- }
這里同樣調(diào)用了全局Function $sliderProcess在設置slider的value值時進行completedbar[width]和slider[left]的更新。由于此處進行了異常處理,所以如果end-user在確保setSliderValue被作用于jSlider對象的時候可以刪除此異常處理代碼。
【皮膚】
根據(jù)jSlider的API我們可以更加方便的為其設定皮膚,為了讓jSlider更加專業(yè),我們需要2張圖片:
用來作為completedbar背景的'bar'和用來作為slider背景的'slider',ok,我們更新下樣式:
- /*----blue skin----*/
- .bluebar
- {
- margin-top: 10px;
- height: 4px;
- background:#F7F7F7;
- border:solid 1px #3e3e3e;
- position: relative;
- }
- .bluebar .jquery-completed
- {
- height: 4px;
- background:url(../images/slider/blue/bar.gif) left center no-repeat;
- top: 0;
- left:0;
- position: absolute;
- }
- .bluebar .jquery-jslider
- {
- height: 17px;
- background:url(../images/slider/blue/slider.gif) center 0 no-repeat;
- top: -4px;
- display: block;
- cursor: pointer;
- position: absolute;
- }
- .bluebar .jquery-jslider-hover
- {
- background-position:center -17px;
- }
由于在設置樣式時我仍然讓子節(jié)點樣式使用了API的默認值,因此在創(chuàng)建jSlider時我們只需要設置barCssName就行了:
- var blue = $.fn.jSlider({
- renderTo: '#slidercontainer',
- size: { barWidth: 500, sliderWidth: 10 },
- barCssName: 'bluebar',
- onChanging: function(percentage, e) {
- // code goes here
- }
- });
呈現(xiàn)出來的UI:
我們這樣來設置其值:
- //set percentage with a callback function
- blue.setSliderValue(0.65, function(percentage) {
- // code goes here
- });
【通用性】
當然,我們不僅可以將jSlider作為slider使用,有時候它也是一個progressbar:
(代碼我就不貼了,直接在demo里查看 ;-) )
【小結(jié)】
通篇到這里就結(jié)束了,簡單的介紹了一款jQuery插件的開發(fā)流程,以及開發(fā)中應該注意的細節(jié),那么在下一篇的文章中我會向大家介紹如何打造一個通用型的 自動完成 插件。
原文鏈接:http://kb.cnblogs.com/page/50095/
【小編推薦】
1.淺析jQuery頁面漸顯效果的實現(xiàn)
2.如何在Java中進行jQuery Json調(diào)用
3.幾分鐘教您擴展jQuery的Json技巧說明