【前言】
jQuery已經被廣泛使用,憑借其簡潔的API,對DOM強大的操控性,易擴展性越來越受到web開發人員的喜愛,我在社區也發布了很多的jQuery插件,經常有人詢問一些技巧,因此干脆寫這麼一篇文章給各位jQuery愛好者,算是拋磚引玉吧。
【基礎】
a)樣式
很 多人會認為樣式是個很復雜的東西,需要沉著冷靜的心態加上非凡的審美觀才能設計出賞心悅目的UI,拋開圖片設計不說,其實css也就是那麼些屬 性:position,margin,padding,width,height,left,top,float,border,background...
UI設計的漂亮與否在很大程度上依賴於設計人員對配色的把握和整體效果的協調。舉個簡單的例子,一個簡單的頁面,馬虎的人:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test Page</title>
</head>
<body>
jQuery是一個框架!壓縮後有30多k吧。
</body>
</html>
細心的人:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test Page</title>
<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 Page</title>
<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都可以輕松的完成,這話是沒錯,當思路確定後,思想才是重點,一段代碼是精華還是糟粕很容易就 可以區分出來,究其原因還是取決你自己,舉個簡單的例子,大量的html組裝,路人甲:
var a = new Array(10);
var menu = '';
for (var i = 0; i < a.length; i++) {
menu += '<li class="style_' + a[i] + '" >' + a[i] + '</li>';
}
路人乙:
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 = '<li class="style_{0}" >{0}</li>';
for (var i = 0; i < a.length; i++) {
menu += m.format(a[i]);
}
在實現方式明確的情況下,優雅高效的代碼顯然更具吸引力。
【實踐】
jQuery開發或使用,更多的靈感是來自實踐,而不是copy||paste(奉行拿來主義的同學可以離開了)。
那麼在這裡我會用一個簡單的例子來闡述jQuery插件開發的流程,能否舉一反三就看各位看官了。
【目的】
開發一個插件之前我們需要對自己的目的有一個清醒的認識,有很明確的方向感,那麼此次我作為示例插件的目的,就是呈現一個用於UI的Slider - 滑動條,常年從事於或暫時專注於win32開發的同學應該比較了解。
草圖
真正動手編碼之前我們還需要有一個草圖來描述自己插件的“長相”(事件驅動或API封裝的可以忽略)。
很多的同學在做UI開發前往往會忙於搜集各種小圖片(非精通ps或iconworkshop人士),其實漂亮的圖標的確可以美化我們的UI,不過我一般的處理方式是編寫易於擴展的css,前期的UI呈現盡量少使用圖片,多用線條完成。
ok,言歸正卷,那麼我的slider設計草圖是:
解釋下下文將用到的幾個詞:
slider: 此部分是作為拖拽手柄來使用,用戶可以通過拖拽此部分來更新completed bar的位置。
completed: 此部分作為bar的內嵌元素,作為特殊效果來顯示slider與起始點的距離,亦即與slider的value值關聯。
bar: slider的載體,completed的滿值。
思路:
slider作為手柄提供拖拽功能,作用區域為bar,拖拽過程中completed條必須實時更新(長度),影響區域為slider至bar左端的距離。
【編碼】
開發jQuery UI/Effect 插件在很多時候都需要與UI交互,因此在呈現上需要提供Html tree來繪制我們的插件,最終通過js dom來輸出,那麼在繪制簡單的dom結構的時候我會直接用js來完成,不過如果嵌套比較復雜的話,我們還是應該先用html來完成,然後轉變成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呈現上我們不使用圖片,盡量用線條、顏色來完成:
/*----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,以方便子節點的浮動(子節點使用position:absolute來獲得內聯浮動效果)。
那麼我們可以看下這個css和html tree產生的UI效果:
ok,具備了所需的元素 - slider, completed, bar.
一些規范:
當我們畫出了UI之後就可以正式編寫jQuery插件代碼了,不過在著之前我們還需要對jQuery插件開發的一些規范性有一些了解。
1. 使用閉包:
(function($) {
// Code goes here
})(jQuery);
這是來自jQuery官方的插件開發規范要求,使用這種編寫方式有什麼好處呢?
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進行插件開發而盡量少使用$.extend.
3. 選擇器
jQuery提供了功能強大,並兼容多種css版本的選擇器,不過發現很多同學在使用選擇器時並未注重效率的問題。
a) 盡量使用Id選擇器,jQuery的選擇器使用的API都是基於getElementById或getElementsByTagName,因此可以知道 效率最高的是Id選擇器,因為jQuery會直接調用getElementById去獲取dom,而通過樣式選擇器獲取jQuery對象時往往會使用 getElementsByTagName去獲取然後篩選。
b) 樣式選擇器應該盡量明確指定tagName, 如果開發人員使用樣式選擇器來獲取dom,且這些dom屬於同一類型,例如獲取所有className為jquery的div,那麼我們應該使用的寫法 是$('div.jquery')而不是$('.jquery'),這樣寫的好處非常明顯,在獲取dom時jQuery會獲取div然後進行篩選,而不是 獲取所有dom再篩選。
c) 避免迭代,很多同學在使用jQuery獲取指定上下文中的dom時喜歡使用迭代方式,如$('.jquery .child'),獲取className為jquery的dom下的所有className為child的節點,其實這樣編寫代碼付出的代價是非常大 的,jQuery會不斷的進行深層遍歷來獲取需要的元素,即使確實需要,我們也應該使用諸如$(selector,context), $('selector1>selector2'), $(selector1).children(selector2), $(selctor1).find(selector2)之類的方式。
開始編碼
話題有點扯遠,ok,在對UI有了清晰的認識後我們就可以使用js來輸出html了。
我們使用jSlider來命名這個slider插件(為了避免插件沖突,插件命名時也應十分考究,這裡我就俗一回)。
$.extend($.fn, {
///<summary>
/// apply a slider UI
///</summary>
jSlider: function(setting) {
}
});
在插件開發中比較標准的方式是將元數據獨立出來並開放API,比如這裡的setting參數傳入值,有時候為了減少代碼編寫量,我習慣於直接在插件內賦值:
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);
規范的做法:
$.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的參數,包括2個值barWidth - bar的長度, sliderWidth - slider的長度。
barCssName: bar的樣式名稱,便於end-user自行擴展樣式。
completedCssName: completed的樣式名稱。
sliderCssName: slider的樣式名稱。
sliderHover: slider聚焦時的樣式名稱。
onChanging: slider被拖拽時觸發的事件。
onChanged: slider拖拽結束時觸發的事件。
此時我們需要將renderTo強制轉換成jQuery對象(兼容使用selector的情況):
ps.renderTo = (typeof ps.renderTo == 'string' ?
$(ps.renderTo) : ps.renderTo);
然後將html tree輸出到render:
/* ---------->
html tree:
<div> ---->sliderbar
<div> </div> ----> completed bar
<div> </div> ----> slider
</div>
<-----------*/
var sliderbar = $('<div><div> </div><div> </div></div>')
.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上直接呈現了Html並且用定制的css進行渲染,分別用sliderbar, completedbar, slider對我們需要的三個對象進行緩存。
ok, 在呈現了UI後我們就需要提供方法來實現slider的拖拽,在這之前我們還需要實現一個方法,就是completedbar的實時更新,即在拖動slider的時候讓completedbar始終填充左側區域:
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左側距離,我給其命名為$sliderProcess。
那麼我們接下來剩下的工作就是slider的拖拽功能了,那麼在這裡我會用到之前發布的一款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方法,向這兩個方 法推送的參數相同,1>百分比,即value值,介於0~1,2>event。
那麼至此我們的jSlider插件就基本成型,向用戶提供了一個可拖拽的slider。
【擴展】
有的時候用戶卻不是那麼容易滿足,於是有人高呼:“我要自己設置value,為什麼不提供這個功能?”。
那麼這時我們就需要為用戶公開一個方法,用於設置jSlider的value,首先考慮的是作為方法需要一個作用對象(jSlider),那麼此時我又不 想將作用對象作為參數傳入,那麼我們還是將這個方法作為插件來開發,我們將方法命名為setSliderValue,開放2個參數,v(value值)和 callback(設置完成後的回調函數)。
即:$.fn.setSliderValue(v,callback);
ok,那麼剩下的就是作用對象了,由之前的設計可知,在slider拖動時主要作用於2個對象,slider和completedbar,那麼我們在jSlider插件末尾加上一段代碼來返回slider對象:
slider.data = { bar: sliderbar, completed: completedbar };
return slider;
這樣我們在初始化jSlider的時候就可以直接用一個變量來獲取jSlider對象,然後調用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);
}
這裡同樣調用了全局Function $sliderProcess在設置slider的value值時進行completedbar[width]和slider[left]的更新。由於此 處進行了異常處理,所以如果end-user在確保setSliderValue被作用於jSlider對象的時候可以刪除此異常處理代 碼。
【皮膚】
根據jSlider的API我們可以更加方便的為其設定皮膚,為了讓jSlider更加專業,我們需要2張圖片:
/*----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;
}
由於在設置樣式時我仍然讓子節點樣式使用了API的默認值,因此在創建jSlider時我們只需要設置barCssName就行了:
var blue = $.fn.jSlider({
renderTo: '#slidercontainer',
size: { barWidth: 500, sliderWidth: 10 },
barCssName: 'bluebar',
onChanging: function(percentage, e) {
// code goes here
}
});
呈現出來的UI:
我們這樣來設置其值:
//set percentage with a callback function
blue.setSliderValue(0.65, function(percentage) {
// code goes here
});
【通用性】
當然,我們不僅可以將jSlider作為slider使用,有時候它也是一個progressbar:
【小結】
通篇到這裡就結束了,簡單的介紹了一款jQuery插件的開發流程,以及開發中應該注意的細節,那麼在下一篇的文章中我會向大家介紹如何打造一個通用型的 自動完成 插件。