一个轻量级的JavaScript库——pj
虽然像jQuery那样的库功能很强大,“品种”齐全,但是即使是jQuery1.4.2压缩版的也有70多kb,甚至比一个网页文件还大。而且有时候我们只需用其中一小部分的功能,不需要将整个jQuery库引进来。但我们又需要一个方便的库来协助开发网页。鉴于此,我就写了一个轻量级的库,命名为pj。很多选择器模式,方法名等引用了jQuery的模式或命名。代码如下:(内容有更新,修复了一些bug;可到http://www.下载最新的(提取码:296a4214-827b-11df-a99d-0015c55db73d),部分效果展示http://users4.)部分代码如下:/*pj.js version: 2.0.6
@copyright: 2010 pengju
email:pengju114@
all rights reserved*/
(function(wnd,undef){
var doc=wnd.document;
var pj=function(selector,context){//selector:选择器, context: 上下文
return new pj.prototype.init(selector,context);
};
pj.prototype={
/***真正的构造函数***/
init:function(sel,cot){
/****上下文默认为document****/
cot=cot&&pj.isOBJ(cot)?cot:doc;
/****保存元素****/
var elems=[];
/****当前pj对象的计时器队列****/
this.timer={};
if(pj.isOBJ(sel))elems.push(sel);
else if(pj.isSTR(sel)){
sel=pj.trim(sel);
/**
*id选择器, 如: pj("#header"),取id为header的元素
*/
if(/^#\w+$/.test(sel))js.id(sel,cot,elems);
/**
*标签 选择器, 如: pj("div"),取页面所有的div
*/
else if(/^\w+$/.test(sel))js.tag(sel,cot,elems);
/**
*className选择器, 如: pj(".ClassName"), 取className为ClassName的元素
*/
else if(/^\.\w+$/.test(sel))(sel,cot,elems);
/**
*取指定id下的所有指定元素
*如: pj("#header>a"), 取id为header元素下的所有a元素
*/
else if(/^#\w+>\w+$/.test(sel)){
var set=sel.split(">");
var t=cot.getElementById(set[0].replace(/^#/,""));
if(t){elems=js.tag(set[1],t,elems);}
}
/**
*取指定标签下面所有指定标签的元素
*如: pj("li>a") 取页面所有li元素下面的a元素
*/
else if(/^\w+>\w+$/.test(sel)){
var tag=sel.split(">");
var set=cot.getElementsByTagName(tag[0]);
if(set.length>0){
for(var k=0;k<set.length;k++)
js.tag(tag[1],set[k],elems);
}
}
/**
*按照指定的元素id、className、或者标签取元素
*如: pj("#header,.name,div") 取id为header的元素
*和className为name的元素和页面所有的div元素
*/
else if(/^([#|\.]?\w+,)+[#|\.]?\w+$/.test(sel)){
js.query(sel,cot,elems);
}
/**
*根据指定的属性或者下标取元素
*如: pj("div[name=value]:0,3") 取页面中含有name属性并且值为value的第一和第四个元素
*/
else if(/^([#|\.]?\w+,)*[#|\.]?\w+(\[(\w+=\w+,)*\w+=\w+\])?(:(\d+,)*\d+)?$/.test(sel)){
var a;
/**
*只指定下标,没有指定属性的情况
*如: pj("div,#header, .name:1,3,6")
*先根据选择器取元素,然后只要第二、第四、第六个元素
*/
if(!/\[(\w+=\w+,)*\w+=\w+\]/.test(sel)){
a=sel.split(":");
var s=[];
js.query(a[0],cot,s);
js.findex(a[1],s,elems);
}
/**
*既指定下标,又指定属性的情况
*如: pj("div,#header, .name[n=v]:1,3,6")
*先根据选择器取元素,然后只要含有属性n并且值为v的第二、第四、第六个元素
*/
else if(/:(\d+,)*\d+$/.test(sel)){
a=sel.split(":");
var all=[];
js.fattr(a[0],cot,all);
js.findex(a[1],all,elems);
}
/**
*只指定属性,没有指定下标的情况
*如: pj("div,#header, .name[name=value,x=y]")
*先根据选择器取元素,然后只要含有属性为name和x并且值为value和y的元素
*/
else {
js.fattr(sel,cot,elems);
}
}
/**
*如果选择器是一个标签,如: <div>或者<div/>
*则创建元素; 创建元素后要调用appendTo方法将元素追加到页面
*否则无法对其进行操作
*/
else if(/^<\w+(\/)?>$/.test(sel)){
sel=sel.replace(/\//,"");
var sub=sel.lastIndexOf(">");
sub=sel.substring(1,sub);
sel=doc.createElement(sub);
if(sel){
elems.push(sel);
}
}
/**
*HTML 标签
*如: <div>内容或者HTML标记</div>
*创建元素并且新建元素的innerHTML=标记间的内容;
*创建元素后要调用appendTo方法将元素追加到页面
*否则无法对其进行操作
*/
else if(/^<(\S*?)[^>]*>.*?<\/\1>|<.*?\/>$/){
var text,tagName;
tagName=sel.indexOf(">");
text=sel.lastIndexOf("<");
text=sel.substring(tagName+1,text);
tagName=sel.substring(1,tagName);
tagName=doc.createElement(tagName);
if(tagName){
tagName.innerHTML=text;
elems.push(tagName);
}
}
}
return pj.merge(this,elems);//将选择原属整合到当前pj对象并返回
},//end init
/****取指定操作对象,
****如果不指明下标
****或越界则返回对
****象数组********
****如果当前操作对
****象只有一个则返
****回一个对象****/
get: function(index){
if(this.length==1)return this[0];
else if(index!==undef)return this[index];
else return Array.prototype.slice.call(this,0);
},
/****以当前所有操作
****对象为上下文执
****行fn函数并返回
****当前pj对象****/
each:function(fn,arg){//arg可选
if(pj.isFn(fn))
for(var i=0;i<this.length;i++){
fn.call(this[i],arg);
}
return this;
},
/****给当前对象集合添加事件监听器***
****参数是一个对象,this总是指向当前DOM对象*****************
****如 pj("selector").addListener({click:function([e]){……}})**/
addListener:function(set){
if(!pj.isOBJ(set))return this;
for(var e in set){
if(wnd.addEventListener) //非IE浏览器
this.each(function(){this.addEventListener(e,set[e],false);});
else if (wnd.attachEvent)//IE浏览器
this.each(function(){this.attachEvent("on"+e,pj.extend(this,set[e]));});
}
return this;
},
attr:function(name/*[,value]*/){//只有一个参数则取属性值,有两个参数则设置属性值
var set=[],arg=arguments;
if(arg.length==1){//get attribute
this.each(function(){set.push(this.getAttribute(name+""));});
return this.length==1?set[0]:set;
}
else if(arg.length==2){//设置属性
this.each(function(){this.setAttribute(name+"",arg[1]+"");});
}
return this;
},
//删除指定的属性
removeAttr:function(name){
if(name)this.each(function(){
try{
this.removeAttribute(name);
}catch(e){
try{
delete this[name];
}catch(et){}
}
});
return this;
},
/**
*停止当前特定的动画,参数是方法名
*如:pj("div").hide().stop("hide")//停止hide动画
*stop方法只能停止自己的动画而不能停止其他pj对象的动画
*每pj('selector')一次就创建一个pj对象
*也就是说如果对一些元素执行相反动画的话(比如在执行hide时要执行show)
*则用一个变量来保存当前的pj对象,用这个变量来调用hide和show方法就会彼此颉颃,互不冲突
*如: var obj=pj("div"); obj.hide(1000);setTimeout(function(){obj.show()},500)
*在hide动画完成之前调用show方法,这样调用就不会冲突;obj会先停止自己的hide动画然后再执行show动画
*如果是pj("div").hide(1000);setTimeout(function(){pj("div").show()},500)就会产生冲突
*/
stop:function(name){//name: 方法名
if(name&&this.timer[name]){
wnd.clearInterval(this.timer[name]);
delete this.timer[name];
}
return this;
},
//将元素追加到指定的DOM元素 ,如果指定的参数不是对象则加到document.body
appendTo:function(target){
var t=doc.body;
if(target&&pj.isOBJ(target)&&target.nodeType&&target.nodeType==1){
t=target;
}
this.each(function(){t.appendChild(this);});
return this;
},
//增加className,不会覆盖原来的
addClassName:function(name){
if(name){
name=" "+name;
this.each(function(){
this.className+=name;
});
}
return this;
},
//删除指定的className,不会删除其他的
removeClassName:function(name){
if(name){
this.each(function(){
this.className=this.className.replace(name,"");
});
}
return this;
},
//清除之前所有的className并设置为指定的
setClassName:function(name){
if(name){
this.each(function(){
this.className=name;
});
}
return this;
},
/**
*删除pj对象中指定的元素,下标从0开始
*indices可以是 all、odd、even、1-5、2-[删除下标为2和以后所有的元素]字符串或者是多个数字
*如 pj("div").cut("even").hide() [或者var obj=pj("div").cut("even");obj.hide()]
*删除下标为偶数的元素并隐藏下标为奇数的元素
*pj("div").cut("0-6").hide() 删除下标0-6的元素并隐藏其余的元素
*pj("div").cut("3-").hide() 删除下标为3和之后所有的元素并隐藏其余的元素
*/
cut:function(indices){
var cache=[],shift=Array.prototype.shift,args=arguments,i;
while(this.length>0)cache.push(shift.call(this));
if(indices=="all")return this;//删除全部
else if(indices=="odd"||indices=="even"){//删除下标为奇数或者偶数DOM对象
for(i=0;i<cache.length;i++){
if(indices=="odd"&&i%2!=0){cache[i]=undef;}
else if(indices=="even"&&i%2==0){cache[i]=undef;}
}
}
else if(/^\d+-(\d+)?$/.test(indices)){//形如 1-3、5-的字符串
var end;
indices=indices.split('-');
indices[0]=parseInt(indices[0]);
indices[1]=parseInt(indices[1]);
if(indices[0]+""=="NaN")indices[0]=cache.length-1;
if(indices[1]+""=="NaN")indices[1]=cache.length-1;
i=Math.min(indices[0],indices[1]);
end=Math.max(indices[0],indices[1]);
for(;i<=end;i++){if(cache[i])cache[i]=undef;}
}
else if(args.length>0){//多个数字 如: 0,2,5,4
for(i=0;i<args.length;i++){
if(typeof args[i]==="number"&&cache[i])cache[args[i]]=undef;
}
}
return pj.merge(this,cache);
}
};//end prototype
//辅佐对象,仅在内部使用
var js={
/****s是形如#id[,#id]……的字符串******
****并以c为上下文取对象,并放到a数组中*/
id:function(s,c,a){
s=s.split(",");
var o;
for(var i=0;i<s.length;i++){
o=c.getElementById(s[i].replace(/^#/,""));
if(o)a.push(o);
}
/****返回对象数组****/
return a;
},
/****s是形如 .className的字符串****/
cname:function(s,c,a){
s=s.replace(/^\./,"");
var x=c.getElementsByTagName("*");
for(var i=0;i<x.length;i++){
if(x[i].className==s)a.push(x[i]);
}
/****返回对象数组****/
return a;
},
/****t是标签****/
tag:function(t,c,a){
t=c.getElementsByTagName(t);
/****返回对象数组****/
return pj.merge(a,t);
},
/****str是形如 #id[.className|tag]... 的字符串****/
query:function(str,c,a){
str=str.split(",");
for(var i=0;i<str.length;i++){
if(/^#\w+$/.test(str[i])){a=this.id(str[i],c,a);}
else if(/^\w+$/.test(str[i])){a=this.tag(str[i],c,a);}
else if(/^\.\w+$/.test(str[i])){a=(str[i],c,a);}
}
return a;
},
/****通过属性过滤元素****/
fattr:function(str,c,to){
str=str.split("[");
var attr=str[1].replace(/\]$/,"").split(",");
var set=[],j,b;
/****str[0]是形如#id|tag|.className...[attr=vlaue]的字符串****/
set=this.query(str[0],c,set);
for(var i=0;i<set.length;i++){
for(j=0;j<attr.length;j++){
b=attr[j].split("=");
try{
if(set[i].getAttribute(b[0])==b[1])to.push(set[i]);
}catch(e){break;}
}
}
},
/****通过下标过滤元素,*********************
****str是形如 0,2,5……的字符串***********
****并从src中选择符合条件的元素复制到to中*/
findex:function(str,src,to){
str=str.split(",");
for(var l=0;l<str.length;l++){
str[l]=parseInt(str[l]);
if(str[l]<src.length)to.push(src[str[l]]);
}
}
};
/****将pj的prototype********
****赋给pj.prototype.init的prototype
**** 这样返回的pj对象就会继承pj.prototype对象中所有的方法 ************/
pj.prototype.init.prototype=pj.prototype;
pj.ready=function(fn){//当页面加载完成时调用fn
if(doc.getElementById&&doc.getElementsByTagName)fn();
else setTimeout(function(){pj.ready(fn);},100);
};
/****target将继承方法fn*****
****并且fn的上下文是target*/
pj.extend=function(target,fn){
return function(){return fn.apply(target,arguments);};
};
/****将一个对象中的属性或方法绑定到指定对象,如果只有一个参数则绑定到pj对象
****也可以通过这个方法自己扩展pj对象
****如 pj.prototype.bind({method:function(){this.each(){alert(this.nodeName)}}[......]})
****这样就可以绑定多个方法到pj对象作用域上,而且所有的pj对象都可以调用
****上面的方法可以这样调用: pj("div").method()**/
pj.bind=pj.prototype.bind=function(){
var tar,src;
if(arguments.length>1){
src=arguments[1];
tar=arguments[0];
}
else if(arguments.length==1){
tar=this;
src=arguments[0];
}
else {
tar={};
src={};
}
for(var e in src){tar[e]=src[e];}
return tar;
};
/******************************内容太多,删了部分************************************/
pj.prototype.bind({
getStyle:function(name){//取元素样式值
var st=[];
this.each(function(){st.push(pj.getStyle(this,name));});
//如果对象集合长度为1则返回属性值,否则返回属性值数组
return st.length==1?st[0]:st;
},
/****设置元素属性,source为对象{color:"red",display:"block"[...]}
****或者source为属性名,value为值****/
setStyle:function(source/*,value*/){
if(arguments.length>1&&!pj.isOBJ(source)){
var name=arguments[0];
source={};
source[name]=arguments[1];
}
if(pj.isOBJ(source))
this.each(function(){pj.setStyle(this,source);});
return this;
}
});
/******************************内容太多,删了部分**************************************/
function now(){
return (new Date()).getTime();
}
/**
*通过当前操作对象保存自身的最初宽度和高度,而且只设置一次,不会删除
*/
function check(cur){
if(!cur.getAttribute("pjheight"))cur.setAttribute("pjheight",pj.wh(cur,"height"));
if(!cur.getAttribute("pjwidth"))cur.setAttribute("pjwidth",pj.wh(cur,"width"));
}
function setDisplay(cur){
//设置最初的display,为none则设置为block,不会删除
if(!cur.getAttribute("pjdisplay")){
var display=pj.getStyle(cur,"display");
cur.setAttribute("pjdisplay",display=="none"?"block":display);
}
}
//实现动画每个属性的渲染器 ,attr属性名、value: 最终值
function render(target,attr,value,duration){
this.t=target;//渲染对象
this.name=pj.trim(attr);//属性名
this.duration=duration; //效果时长
this.unit="px"; //默认单位
var v,overflow="",pos="";
var display=pj.getStyle(this.t,"display");
if(/^left$/i.test(this.name)||/^top$/i.test(this.name)){
pos="absolute"; //left、top动画则设置position为absolute
var left=pj.x(this.t),top=pj.y(this.t);
if(/^left$/i.test(this.name))v=left;
else v=top;
pj.setStyle(this.t,{"left":left+this.unit,"top":top+this.unit});
}
else if(/^width$/i.test(this.name)||/^height$/i.test(this.name)){
overflow="hidden";
if(/^width$/i.test(this.name))v=pj.wh(this.t,"width");
else v=pj.wh(this.t,"height");
//height或者width动画并且可见则设置display为block
display=display=="none"?display:"block";
}
else v=parseInt(pj.getStyle(this.t,this.name));
this.begin=(v+""=="NaN")?0:v;//开始值
if(typeof value==="number")this.span=value-this.begin;//变化区间
else if(pj.isSTR(value)){
value=pj.trim(value);
var spt=/^([+-]=)?([\d+-.]+)(.*)$/.exec(value);//分析类似于 +=200px、-=150%的字符串
if(spt[1]=="+=")this.span=parseInt(spt[2]);
else if(spt[1]=="-=")this.span=-parseInt(spt[2]);
else this.span=parseInt(spt[2])-this.begin;
if(spt[3]&&spt[3]!="")this.unit=spt[3];
}
else this.span=0;
pj.setStyle(this.t,{"position":pos,"overflow":overflow,"display":display});
this.next=function(start,currentTime){ //下一帧动画
var val=currentTime-start; // 经过的时间
val=Math.min(val,this.duration)/this.duration;//动画的进度
/**** sin曲线。从-PI/2 到PI/2 ,变化区间 0-2 ****/
/**** 除2确保变化区间为0-1 *********************/
val=(1+Math.sin((-Math.PI*0.5)+val*Math.PI))/2;/*使得动画在两端慢,在中间快*/
val=val*this.span;
val=this.begin+val;
try{
this.t.style[this.name]=val+this.unit;//有时值小于0会发生异常
}catch(e){
//this.t.style[this.name]=this.begin+this.span+this.unit;
}
};
}
function opacity(target,value,duration){
this.t=target;//渲染对象
this.duration=duration;
var v=parseFloat(pj.getStyle(this.t,"opacity"));//取当前透明度
v=this.t.filters?v:v*100;
if(!this.t.getAttribute("pjopacity")){
this.t.setAttribute("pjopacity",v);
}
this.begin=v;
if(typeof value==="number")this.span=value-this.begin;
else if(pj.isSTR(value)){
value=pj.trim(value);
var spt=/^([+-]=)?([\d+-.]+)(.*)$/.exec(value);
if(spt[1]=="+=")this.span=parseInt(spt[2]);
else if(spt[1]=="-=")this.span=-parseInt(spt[2]);
else this.span=parseInt(spt[2])-this.begin;
}
else this.span=0;//不做任何事
this.next=function(start,currentTime){
var val=currentTime-start;
val=Math.min(val,this.duration)/this.duration;
val=val*this.span;
val=this.begin+val;
pj.setOpacity(this.t,val);
};
}
pj.prototype.bind({//绑定效果方法
/****自定义动画 **************************************************
**** styleSet:属性集合[如:{left:"+=80px",top:"-=33",width:330}] **
**** duration:效果时长 *******************************************
**** fn:完成后的回调函数 *****************************************
**** hide:完成后是否隐藏元素 **************************************
**** 如: pj("#R_left").animate({width:100,left:-10,top:50},800)*/
animate:function(styleSet,duration,fn,hide){
var i,j=0;
var operator={};//保存进行加工对象的集合(操作集合)
var name=arguments[4]||"animate"; //动画方法名,方便stop
if(this.timer[name])return this;
duration=duration||400;//默认效果时长为400毫秒
fn=fn||function(){};
var v,cur;
for(i=0;i<this.length;i++){
setDisplay(this[i]); //设置最初display
check(this[i]); //设置最初高度和宽度
for(var e in styleSet){
v=pj.isArray(styleSet[e])?styleSet[e][i]:styleSet[e];//属性值可以是数组
if(/opacity/i.test(e)){//分开处理目的是为了提高速度,减少判断
operator[j++]=new opacity(this[i],v,duration);
}
else{
operator[j++]=new render(this[i],e,v,duration);
}
}
}
/****start:动画开始的时间,currentTime:当前时间****/
var start=now(),currentTime;
cur=this;//当前pj对象的引用
this.timer[name]=wnd.setInterval(function(){
currentTime=now(); //每进行一帧则取当前时间
for(j in operator){//执行动画的下一帧
operator[j].next(start,currentTime);
}
if(currentTime-start>=duration){//动画完成
wnd.clearInterval(cur.timer[name]);//清除timer
delete cur.timer[name];
if(hide)cur.setStyle({display:"none"});
else{ //完成后不隐藏则恢复原来的display
cur.each(function(){
this.style.display=this.getAttribute("pjdisplay")||"block";
});
}
fn(cur);/****执行回调函数****/
}
},15);//每15毫秒执行一帧动画
return this;
},//end animate
/**
*参数都可选;h:最终位置或者是类似于+=22或-=55等的字符串
*如果元素的高度(或者宽度)需要随需要变化,比如一个DIV要动态地接收来自Ajax的返回内容
*则在动画之前先将pjheight(或者pjwidth)属性删除,然后添加内容,再进行动画
*用法类似于 pjObj.removeAttr("pjheight").setStyle({"height":"auto"}).get(0).innerHTML=content;
*pjObj.slideDown();
*/
slideDown:function(duration,h,fn){
//如果当前pj对象正在slideDown则返回
if(this.timer["slideDown"])return this;
var vs=[];//保存各个元素的最初高度
/**
*如果对一些元素执行相反动画的话(比如在执行slideDown时要执行slideUp)
*则用一个变量来保存当前的pj对象,用这个变量来调用slideDown和slideUp方法
*就会彼此颉颃,互不冲突 如: var obj=pj("div");
*obj.slideDown(1000);setTimeout(function(){obj.slideUp()},500)
*在slideDown动画完成之前调用slideUp方法,这样调用就不会冲突;
*obj会先停止自己的slideDown动画然后再执行slideUp动画
*/
return this.stop("slideUp").each(function(){
check(this);
setDisplay(this);
vs.push(parseInt(this.getAttribute("pjheight")));
if(pj.getStyle(this,"display")=="none")
pj.setStyle(this,{"height":"0px","display":"block"});
}).animate({"height":h||vs},duration,fn,false,"slideDown");
},
//参数都可选;h:最终位置或者是类似于+=22或-=55等的字符串
slideUp:function(duration,h,fn){
if(this.timer["slideUp"])return this;//如果当前pj对象正在slideUp则返回
h=h||0;
return this.stop("slideDown").animate({"height":h},duration,fn,h<=0,"slideUp");
},
/************************内容太多,删了部分**************************************/
//参数都可选
hide:function(duration,fn){
if(this.timer["hide"])return this;
/**
*如果对一些元素执行相反动画的话(比如在执行hide时要执行show)
*则用一个变量来保存当前的pj对象,用这个变量来调用hide和show方法就会彼此颉颃,互不冲突
*如: var obj=pj("div"); obj.hide(1000);setTimeout(function(){obj.show()},500)
*在hide动画完成之前调用show方法,这样调用就不会冲突;
*obj会先停止自己的hide动画然后再执行show动画
*如果是pj("div").hide(1000);setTimeout(function(){pj("div").show()},500)
*就会产生冲突
*/
return this.stop("show").each(
function(){
//pjopacity只设置一次而且不会删除
if(!this.getAttribute("pjopacity")){//用当前DOM对象记录自己最初的opacity值
var v=parseFloat(pj.getStyle(this,"opacity"));
this.setAttribute("pjopacity",this.filters?v:v*100);
}
}).animate({"width":0,"height":0,"opacity":0},duration,fn,true,"hide");
},
//参数都可选
show:function(duration,fn){
if(this.timer["show"])return this;
var opu=[],w=[],h=[],value;
return this.stop("hide").each(
function(){
check(this); //设置pjwidth和pjheight
setDisplay(this);
w.push(parseInt(this.getAttribute("pjwidth")));
h.push(parseInt(this.getAttribute("pjheight")));
value=this.getAttribute("pjopacity")
if(value){
value=parseFloat(value);
}
else{
value=parseFloat(pj.getStyle(this,"opacity"));
value=this.filters?value:value*100;
this.setAttribute("pjopacity",value);//pjopacity只设置一次而且不会删除
}
opu.push(value)
if(pj.getStyle(this,"display")=="none")
pj.setStyle(this,{"width":"0px","height":"0px","opacity":0,"display":"block"});
}).animate({"width":w,"height":h,"opacity":opu},duration,fn,false,"show");
}
/******************************内容太多,删了部分************************************/
});
/****将pj映射到全局对象上****/
wnd.pj=pj;
})(window);