本篇為完結篇。主要講述如何造出輪子的高級特性。 一. css方法的高級操作 先看本文第一部分所講的dQuery css方法 javascript //css方法 dQuery.prototype.css=function(attr,value){ if(arguments.length==2){// ...
本篇為完結篇。主要講述如何造出輪子的高級特性。
一. css方法的高級操作
先看本文第一部分所講的dQuery css方法
//css方法
dQuery.prototype.css=function(attr,value){
if(arguments.length==2){//當參數個數為2時,使用設置css的方法
var i=0;
for(i=0;i<this.elements.length;i++){
this.elements[i].style[attr]=value;
}
}else{//只有一個參數時獲取樣式
return getStyle(this.elements[0],attr);
}
};
在這個方法里,只能一次一行,且只能設置一個屬性。效率還不夠高。現在嘗試通過設置類似$d('#div1').css({width:'200px',height:'200px',background:'red'})
這樣的方式,向css方法傳入一個json對象。允許一次性設置多個css樣式。或者允許鏈式操作。
1. 傳入json數據
傳入json實質上是一個參數。所以只有一個參數時,不僅僅是獲取,還可能是傳入了json數據。需要對傳入一個參數時,進行分類討論
json與for-in迴圈
var json={width:'200px',height:'200px',background:'red'};
var i=0;
for(i in json){
alert(i+':'+json[i]);
}
這段程式可以依次彈出i(json屬性):json數據的視窗。
同理,在dQuery的css方法中也可以這麼做。
//css方法
dQuery.prototype.css=function(attr,value){
if(arguments.length==2){//當參數個數為2時,使用設置css的方法
var i=0;
for(i=0;i<this.elements.length;i++){
this.elements[i].style[attr]=value;
}
}else if(arguments.length==1){//只有一個參數時獲取樣式,或傳入json
if(typeof attr=='string'){//attr為純文字時,設置樣式
return getStyle(this.elements[0],attr);
}else{//傳入json,批量設置css樣式
for(i=0;i<this.elements.length;i++){
var j='';
for(j in attr){
this.elements[i].style[j]=attr[j];
}
}
}
}
};
測試成功。
#### (2)鏈式操作
函數鏈式操作原理:返回函數本身。
function show(str){
alert(str);
return show;//關鍵
}
show('abc')('bcd');
這段代碼將彈出兩個框,abc,和bcd。只要你想,可以無限地寫下去。
鏈式操作是jquery的主要特色。執行完一個方法之後,又返回到該對象。所以只需要在css函數的最後,補上return this
。就完成了。根據這個思想,可以給每個方法加上return this
,那麼你的js庫就可以實現完全的鏈式操作。
二. attr設置進一步——removeClass和addClass的實現
之前說到attr方法本質上和css方法一樣的。所以attr也可以用類似的方法設置——
//attr方法和css方法類似。
dQuery.prototype.attr=function(attr,value){
if(arguments.length==2){//設置屬性
var i=0;
for(i=0;i<this.elements.length;i++){
this.elements[i][attr]=value;
}
}else if(arguments.length==1){//獲取屬性
if(typeof attr=='string'){
return this.elements[0][attr];
}else{
for(i=0;i<this.elements.length;i++){
var j='';
for(j in attr){
this.elements[i][j]=attr[j];
}
}
}
}
return this;
}
這段attr方法已經能夠實現鏈式操作和接收json數據。現在需要利用attr方法實現添加class和移除class的功能。
jquery中,addClass
和removeClass
的基本功能是:給addClass方法傳入一個字元串,目標元素的class尾部追加這段字元串,給removeClass傳入字元串,就把該字元串從目標元素的字元串中刪除掉。
用文字描述之後,實現就簡單不少了。
//addClass和removeClass的實現
dQuery.prototype.addClass=function(str){
var i=0;
for(i=0;i<this.elements.length;i++){
if(this.elements[i].className==''){
this.elements[i].className+=str;
}else{
this.elements[i].className+=' '+str;
}
}
return this;
}
dQuery.prototype.removeClass=function(str){
var i=0;
for(i=0;i<this.elements.length;i++){
var _className=this.elements[i].className
if(!str){//如果不傳參,所有class都被清空。
this.elements[i].className='';
}else if(_className!=''){
var arr=_className.split(' ');
var j=0;
for(j=0;j<arr.length;j++){
if(arr[j]==str){
arr.splice(j,1);//從數組第j個起刪除1個(移除arr[j])
this.elements[i].className=arr.join(' ');//把數組重新轉化為字元串,並用空格分開。最後賦值給當下對象的className。
}
}
}
}
return this;
}
註意,此段代碼有局限性,前提是操作者html操作正確時才能生效,類似$d('.div1').addClass('div1').removeClass('div1')
雖然也能嘗試理解操作者意思並容錯。但是<div class="div1 div1 div2">
這樣錯誤的class標記使用removeClass會發生錯誤。
3.阻止冒泡及預設事件
右鍵菜單(contextmenu)為例——jquery阻止預設事件,冒泡的機制是
$(function(){
$(document).bind('contextmenu',function(){//對document綁定contextmenu事件,並執行函數。
return false;
})
})
return false在原生js中只處理阻止預設事件,但jquery可以兩樣都阻止。
首先,dQuery沒有bind方法。加上去就行。所謂綁定事件的框架很簡單,實際上和dQuery其它事件的框架是一樣的。
//綁定事件的方法:
dQuery.prototype.bind=function(sEv,fn){
var i=0;
for(i=0;i<this.elements.length;i++){
myAddEvent(this.elements[i],sEv,fn);
}
}
只要你在頁面上右鍵點擊,就會觸發函數。然而還不完善。
$d(function (){
$d(document).bind('contextmenu', function (){
return false;
});
});
這裡添加了return false,但右擊,無法阻止任何事件。
究其深層原因,可以發現,回調函數是通過myAddEvent()這個函數實現的。
這就搞笑了。在myAddEvent()中,無論return什麼都沒有意義。所以要實現,完整的myAddEvent()代碼是:
//可重覆調用的載入函數
function myAddEvent(obj,sEv,fn){
if(obj.attachEvent){
obj.attachEvent('on'+sEv,function(){
if(false==fn.call(obj)){//當調用return false的時候
event.cancelBubble=true;//阻止冒泡
return false;
}
});
}else{
obj.addEventListener(sEv,function(ev){
if(false==fn.call(obj)){
ev.cancelBubble=true;//阻止冒泡
ev.preventDefault();//火狐、chrome下用於阻止預設事件的語句
}
},false);
}
}
這樣,在三大瀏覽器下,已經實現了事件的綁定調用return false無預設事件。並且阻止冒泡。
三. 插件介面
插件需要一個機制。可以通過它自定義方法名稱,方法內容.
假設頁面有三個class為div1的div,擴展s一個size
方法,獲取某類頁面元素的個數:
dQuery.prototype.extend=function (name, fn){
dQuery.prototype[name]=fn;
};
$d().extend('size',function(){
alert(this.element.length);
})
//調用插件里的方法和內部方法無差異
$d('.div1').size();
彈出結果就為3.
從這個角度說,插件機制編寫方法似乎更加便捷了。
1. animate運動框架
jquery的運動形式為:xxx.animate('',目標)。目標是一個json數據。
在運動學教程中有一個比較完美的運動框架startMove。startMove的json傳入的值為最終運動狀態。註意:和jquery不同,數值不帶單位,透明度的量度為100而不是1.
$d().extend('animate',function(json){
var i=0;
for(i=0;i<this.elements.length;i++){
console.log(this.elements);
startMove(this.elements[i],json,function(){alert('hehe')})
}
function getStyle(obj, attr)
{
if(obj.currentStyle)
{
return obj.currentStyle[attr];
}
else
{
return getComputedStyle(obj, false)[attr];
}
}
function startMove(obj,json,fn){
clearInterval(obj.timer);
obj.timer=setInterval(function(){
var bStop= true;//標志著所有運動都結束了
//遍歷每個json屬性
for(var attr in json){
//取當前的屬性對象
var iCur=0;
if(attr=='opacity'){
iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
}else{
iCur=parseInt(getStyle(obj,attr));
}
//定義速度值
var iSpeed=(json[attr]-iCur)/8;
iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);
//檢測停止:如果我發現某個值不等於目標點bStop就不能為true。
if(iCur!==json[attr]){
bStop=false;
}
//計算
if(attr=='opacity'){
obj.style[attr]=(iCur+iSpeed)/100;
obj.style.filter='alpha(opacity:'+(iSpeed+iCur)+')';
}else{
obj.style[attr]=iCur+iSpeed+'px';
}
}
//檢測是否停止,是的話關掉定時器
if(bStop==true){
if(iCur==json[attr]){
clearInterval(obj.timer);
if(fn){fn();};
}
}
},20)
}
});
那麼這個插件就寫完了。
【案例分析】上下滑動的幻燈片(選項卡)
基本思路和之前的選項卡案例差不多。但是切換過程是上下滑動的。因此所有圖片不能隱藏,應該是一張借一張縱向連接。運動是由一個大容器帶著一起運動。垂直運動距離由index()值決定同時,有了addClass和removeClass方法,可以設置一個class='active'的樣式。
<div id="tab">
<ul class="list_group">
<li><a href="javascript:;">1</a></li>
<li><a href="javascript:;">2</a></li>
<li><a href="javascript:;">3</a></li>
<li><a href="javascript:;">4</a></li>
</ul>
<div class="box">
<div class="box2">
<div class="content">
<img alt="1" src="images/1.jpg">
</div>
<div class="content">
<img alt="2" src="images/2.jpg">
</div>
<div class="content">
<img alt="3" src="images/3.jpg">
</div>
<div class="content">
<img alt="4" src="images/4.jpg">
</div>
</div>
</div>
</div>
css
*{
margin:0;
padding: 0;
}
ul{
list-style: none;
}
a{text-decoration: none;}
#tab{
width: 400px;
margin:100px auto;
position: relative;
}
.list_group li{
float: left;
}
.list_group li a{
display: block;
width: 40px;
line-height: 30px;
text-align: center;
}
.box{
clear: both;
width: 402px;height: 302px;
overflow: hidden;
position: relative;
}
.box2{
position: absolute;
top:0;
}
.content{
width: 402px;
height: 302px;
}
.content img{
border: 1px solid black;
width: 400px;height: 300px;
}
.active{
background: #ccc;
}
javascript
$d(function(){
$d('li').click(function(){
var index=$d(this).index();
var moveLength=-302*index;
$d('.box2').animate({top:moveLength});
$d('li').removeClass('active');
$d(this).addClass('active');
})
})
效果:
效果還是差強人意,可以做自動播放:
$d().extend('size',function(){
return this.elements.length;
})//定義一個統計元素個數的dQuery插件
$d(function(){
var iNow=0;
var timer=null;
//定義一個函數指令,可以移動.box2 -302*iNow個單位。
function tab(){
var moveLength=-302*iNow;
$d('.box2').animate({top:moveLength});
$d('li').removeClass('active');
$d('li').eq(iNow).addClass('active');
}
//自動播放定義定時器內的函數
function timerInner(){
iNow++;
if(iNow==$d('li').size()){
iNow=0;
}
tab();
}
timer=setInterval(timerInner,1000);//調用定時器
//點擊事件函數
$d('li').click(function(){
iNow=$d(this).index();
tab();
});
//滑鼠移入選項卡範圍,關掉定時器timer,移出時再打開
$d('#tab').hover(function(){
clearInterval(timer);
},function(){
timer=setInterval(timerInner,1000)
})
})
效果更加人性化了。
小結:與前一個版本的dQuery庫相比,寫法更進一步。
2.拖拽插件
加入頁面上有一個絕對定位的div#div1
樣式如下:
#div1{
width: 100px;height: 100px;
background: red;
position: absolute;
}
引入方法是:
$d().extend('drag', function (){
var i=0;
for(i=0;i<this.elements.length;i++){
drag(this.elements[i]);
}
function drag(oDiv){//拖拽函數
oDiv.onmousedown=function (ev){
var oEvent=ev||event;
var disX=oEvent.clientX-oDiv.offsetLeft;
var disY=oEvent.clientY-oDiv.offsetTop;
document.onmousemove=function (ev){
var oEvent=ev||event;
oDiv.style.left=oEvent.clientX-disX+'px';
oDiv.style.top=oEvent.clientY-disY+'px';
};
document.onmouseup=function (){
document.onmousemove=null;
document.onmouseup=null;
};
};
}
});
調用方法:
$d('#div1').drag();
一個簡單到令人髮指的效果就做好了。
附錄
1.dQuery基本代碼(dQuery.js)
//可重覆調用的載入函數
function myAddEvent(obj,sEv,fn){
if(obj.attachEvent){
obj.attachEvent('on'+sEv,function(){
if(false==fn.call(obj)){//當調用return false的時候
event.cancelBubble=true;
return false;
}
});
}else{
obj.addEventListener(sEv,function(ev){
if(false==fn.call(obj)){
ev.cancelBubble=true;
ev.preventDefault();//火狐、chrome下用於阻止預設事件的語句
}
},false);
}
}
//class選擇器調用函數
function getByClass(oParent,sClass){
var aEle=oParent.getElementsByTagName('*');//選擇父元素的所有元素
var aResult=[];
var re=new RegExp('\\b'+sClass+'\\b','i');//正則邊界
var i=0;
for(i=0;i<aEle.length;i++){
if(re.test(aEle[i].className)){
aResult.push(aEle[i]);
}
}
return aResult;
}
//獲取計算後的樣式
function getStyle(obj,attr){
//元素,樣式
if(obj.currentStyle){//相容ie9及以下
return obj.currentStyle[attr];
}else{
return getComputedStyle(obj,false)[attr];
}
}
//定義dQuery對象
function dQuery(vArg){//參數是變體變數
this.elements=[];//選擇器選擇的元素扔到這個數組中
switch(typeof vArg){
//如果參數是函數
case 'function':
myAddEvent(window,'load',vArg);
break;
//如果參數是字元串
case 'string':
switch(vArg.charAt(0)){
case '#'://id選擇器參數應該為#號之後的字元段
var obj=document.getElementById(vArg.substring(1));
this.elements.push(obj);
break;
case '.'://class
this.elements=getByClass(document,vArg.substring(1));
break;
default://標簽
this.elements=document.getElementsByTagName(vArg);
}
break;
//如果參數是對象。
case 'object':
this.elements.push(vArg);
}
}
//定義簡寫
function $d(vArg){
return new dQuery(vArg);
}
//對選擇器函數綁定click事件
dQuery.prototype.click=function(fn){
var i=0;
//對於返回器數組的內容
for(i=0;i<this.elements.length;i++){
myAddEvent(this.elements[i],'click',fn);
}
return this;
}
//對選擇器函數綁定show/hide事件
dQuery.prototype.show=function(){
var i=0;
//對於返回器數組的內容
for(i=0;i<this.elements.length;i++){
this.elements[i].style.display='block';
}
return this;
}
dQuery.prototype.hide=function(){
var i=0;
//對於返回器數組的內容
for(i=0;i<this.elements.length;i++){
this.elements[i].style.display='none';
}
return this;
};
//hover方法
dQuery.prototype.hover=function(fnover,fnout){
var i=0;
//對於返回器數組的內容
for(i=0;i<this.elements.length;i++){
//給這個對象一次性綁定兩個事件
myAddEvent(this.elements[i],'mouseover',fnover);
myAddEvent(this.elements[i],'mouseout',fnout);
}
return this;
};
//css方法
dQuery.prototype.css=function(attr,value){
if(arguments.length==2){//當參數個數為2時,使用設置css的方法
var i=0;
for(i=0;i<this.elements.length;i++){
this.elements[i].style[attr]=value;
}
}else if(arguments.length==1){//只有一個參數時獲取樣式,或傳入json
if(typeof attr=='string'){//attr為純文字時,設置樣式
return getStyle(this.elements[0],attr);
}else{//傳入json,批量設置css樣式
for(i=0;i<this.elements.length;i++){
var j='';
for(j in attr){
this.elements[i].style[j]=attr[j];
}
}
}
}
return this;
};
//toggle方法:
dQuery.prototype.toggle=function(){
var _arguments=arguments;//把toggle的arguments存起來,以便在其它函數中可以調用。
//私有計數器,計數器會被一組對象所享用。
function addToggle(obj){
var count=0;
myAddEvent(obj,'click',function(){
_arguments[count++%_arguments.length].call(obj);
})
}
var i=0;
for(i=0;i<this.elements.length;i++){
addToggle(this.elements[i]);
}
return this;
}
//attr方法和css方法類似。
dQuery.prototype.attr=function(attr,value){
if(arguments.length==2){//設置屬性
var i=0;
for(i=0;i<this.elements.length;i++){
this.elements[i][attr]=value;
}
}else if(arguments.length==1){//獲取屬性
if(typeof attr=='string'){
return this.elements[0][attr];
}else{
for(i=0;i<this.elements.