1 知识点复习
- 事件
- 事件分为DOM0级事件和DOM2级事件
- DOM0级事件:
oDiv.onclick=函数名/匿名函数
- 缺点:1)一个元素一种行为只能绑定一个方法,不能多次绑定;2)当事件触发时,在标准浏览器(包括IE9,10)下会默认向函数名或匿名函数中,传入一个事件对象实参,在IE8及其以下浏览器中,不会默认传入实参;
- DOM2级事件:通过
addEventListener()
和attachEvent()
来绑定;- 缺点:在IE浏览器下用attachEvent绑定会出现三个问题:1)函数中this执行window,不是元素;2)同一个方法可以重复绑定上,然后执行多次;3)执行时不按绑定的顺序执行,为乱序执行;
- 封装event事件库
- 目的:解决IE浏览器向DOM2级绑定中attachEvent的问题
- 封装结果:同一个元素,同一个行为可以绑定多个方法,并且保证函数执行中this指向元素,顺序执行,并且向函数中默认传事件对象实参,如果函数中需要使用事件对象,就设置形参e,如果不需要,则不设置;
- bind知识点
- bind方法:指的是在标准浏览器(包括IE9,10)下对函数的预处理,可改变函数的this指向,传实参,当与事件配合使用时,默认传事件对象实参;
- 缺点:IE8及其以下浏览器不支持bind属性
- 封装myBind方法库
- 目的:达到与bind一样的效果,和相同使用方式;
- 封装结果:与bind的使用方式相同,效果一样;即在所有浏览器下兼容;改变函数的this指向,传入实参,与事件配合使用时,默认传事件对象实参,事件对象已经兼容处理;
- bind方法:指的是在标准浏览器(包括IE9,10)下对函数的预处理,可改变函数的this指向,传实参,当与事件配合使用时,默认传事件对象实参;
- 事件赋值的不同情况
- DOM0级事件:
- 需求:存在一个函数名为add的函数,当点击事件发生时,执行add函数并改变其this指向和获取实参,包括事件对象
- 两种方法:
- 1)给事件添加匿名函数,匿名函数中用call来改变this指向,但是要注意的是事件对象的兼容处理,在标准浏览器下,事件触发,会默认给匿名函数传事件对象实参,但IE浏览器下不会传事件对象;
- 代码:
<script> var oDiv=document.getElementById("div1"); function add(n,m,e){ console.log(this); console.log(n+m); console.log(e.clientX,e.clientY); console.log(e.pageX,e.pageY); } oDiv.onclick=function (e) { if(!e){//当为IE浏览器时,不会传e,所以e此时为undefined,需要重新获取事件对象,然后做兼容处理,与标准浏览器相同; e=window.event; //IE浏览器下的兼容处理 e.target=e.srcElement; e.pageX=(document.documentElement.scrollLeft ||document.body.scrollLeft)+e.clientX; e.pageY=(document.documentElement.scrollTop || document.body.scrollTop)+e.clientY; //阻止默认事件 e.preventDefault=function () { e.returnValue=false; }; //阻止冒泡 e.stopPropagation=function () { e.cancelBubble=true; }; } add.call(this,2,3,e);//此时通过call来改变this指向,传参 } </script>
- 2)利用封装的myBind方法:所有浏览器都兼容,事件对象也兼容
- 代码:
<body> <div id="div1"></div> <script src="JS/01event.js"></script> <script src="JS/02myBind.js"></script> <script> var oDiv=document.getElementById("div1"); function add(n,m,e){ console.log(this); console.log(n+m); console.log(e.clientX,e.clientY); console.log(e.pageX,e.pageY); } //myBind方法,不用传e; oDiv.onclick=add.myBind(oDiv,2,3); </script> </body>
- DOM2级事件:
- 实质:使用封装的event库,来绑定事件,事件触发时,会给使函数中的this指向为该元素,默认传入一个事件对象实参;兼容所有浏览器;
- 需求:存在一个函数名为add的函数,当点击事件发生时,执行add函数并改变其this指向和获取实参,包括事件对象
- 注意:利用myBind绑定时,直接解绑函数名,无法解绑,需要新建一个变量,然后解绑变量,变量就代表一个匿名函数;
<body> <div id="div1">111</div> <script src="JS/01event.js"></script> <script src="JS/02myBind.js"></script> <script> var oDiv=document.getElementById("div1"); function add(n,m,e){ console.log(this.innerHTML); console.log(n+m); console.log(e.clientX,e.clientY); console.log(e.pageX,e.pageY); } //1 利用匿名函数绑定 on(oDiv,"click",function (e) { add.call(this,2,3,e); }); //2 利用myBind函数 var arr=add.myBind(oDiv,2,3);//将地址赋值变量,然后解绑变量; on(oDiv,"click",arr); off(oDiv,"click",arr);//解绑 </script> </body>
- DOM0级事件:
2 拖拽实例
2.1 es6版面向对象的纯净版拖拽实例
- 知识点:
- processAge函数封装:改变函数中的this指向,注意事件对象的传参
function processAge(fn,thisArg) { return function (e) { fn.call(thisArg,e); } }
- 使用事件库绑定事件时,绑定的函数为匿名函数,不能解绑,所以需要新建一个变量来赋值匿名函数,然后解绑变量;
this.MOVE=processAge(this.move,this); on(this.ele,"mousemove",this.MOVE); off(this.ele,"mousemove",this.MOVE);
- 代码:
- 执行代码:
<script> var oDiv=document.getElementById("div1"); var drag=new Drag({ ele:oDiv }) </script>
- JS封装代码:
class Drag{ constructor(opt){ opt=opt||{}; if(!opt.ele) return; this.ele=opt.ele; this.disX=null; this.disY=null; this.maxL=null; this.maxT=null; this.DOWN=null; this.MOVE=null; this.UP=null; this.init(); } init(){ this.DOWN=processAge(this.down,this); on(this.ele,"mousedown",this.DOWN); } down(e){ //通过元素位置计算出光标相对于元素内的位置 disX disY var l=this.ele.offsetLeft; var t=this.ele.offsetTop; var x=e.clientX; var y=e.clientY; this.disX=x-l; this.disY=y-t; this.MOVE=processAge(this.move,this);//此时为一个匿名函数; this.UP=processAge(this.up,this); //添加事件 if(this.ele.setCapture){//IE浏览器设置焦点捕获 this.ele.setCapture(); on(this.ele,"mousemove",this.MOVE); on(this.ele,"mouseup",this.UP); }else{//标准浏览器下,给document设置事件,阻止默认事件 on(document,"mousemove",this.MOVE); on(document,"mouseup",this.UP); e.preventDefault();//阻止默认事件;防止选中文字; } } move(e){ //边界值判断 var l=e.clientX-this.disX; var t=e.clientY-this.disY; this.maxL=(document.documentElement.clientWidth || document.body.clientWidth)-this.ele.offsetWidth; this.maxT=(document.documentElement.clientHeight || document.body.clientHeight)-this.ele.offsetHeight; if(l<=0){ l=0; }else if(l>=this.maxL){ l=this.maxL; } if(t<=0){ t=0; }else if(t>=this.maxT){ t=this.maxT; } //设置新位置 this.ele.style.left=l+"px"; this.ele.style.top=t+"px"; } up(){ if(this.ele.releaseCapture){ this.ele.releaseCapture();//释放焦点捕获 off(this.ele,"mousemove",this.MOVE); off(this.ele,"mouseup",this.UP); }else{ off(document,"mousemove",this.MOVE); off(document,"mouseup",this.UP); } } }
2.2 弹性运动拖拽实例
- 弹性运动的实现本质:
- X轴方向:
- 在移动事件move函数中,获取每次运动的光标位置与上一次的光标位置之间的距离,作为运动速度,通过乘以小于1的参数,来不断的缩小;
- 在鼠标抬起事件up的函数中,执行一个函数dropX;
- 在dropX函数中,设置物体的left值,使其加上移动的速度值;通过定时器来不断的更新位置;直到运动速度值的绝对值小于一定值(一般为0.5)后,定时器不再运行;
- 在drop函数中,要判断left的边界值,当达到0或maxL时,设置运动速度承等-1,这样就会实现反弹的效果;
- Y轴方向:
- 在鼠标抬起事件up的函数中,执行一个函数dropY;
- 在dropY函数中,设置下落的速度值,给其赋值初始值为9.8,指的是重力加速度值,可以赋值其他值;然后定时器开启再次执行dropY函数时,给速度值累加9.8,然后给其承等小于1的参数;然后设置top值
- 通过边界值的判断,来使速度值乘等-1,达到反弹效果;
- 当物体下落到最底部时,定时器还在运行,通过设置一个开关stemp来控制定时器的运行;
- X轴方向:
- 知识点:
- 设置
this.speedX*=0.93;
和this.speedY*=0.98
,指的就是让speed的值不断的减少,然后才会达到停下来的效果,如果给其乘等1,那么物体就不会停下来,一直反弹; - 边界值判断后,设置
this.speedY*=-1
,才能达到反弹的效果; - X轴方向dropX函数中定时器不再执行的条件:判断speedX的值小于一定值,然后使其不再执行;
- Y轴方向dropY函数中定时器不再执行的条件:在边界值判断中,利用一个变量stemp来控制,当物体开始下落过程中t的实时值会小于maxT值,当t的值大于maxT值后,stemp赋值为1,然后会被反弹,t值小于maxT值,stemp又会被赋值为0;直到物体的值持续大于maxT值后,stemp一直累加,大于2后,定时器不再执行;
- 当物体在经过反弹后,speedY值开始从负数累加定值,然后当speedY值大于0时,达到最高点,然后继续累加,然后下降;
- 设置
- 注意点:
- 在设置定时器后,每次在执行dropX和dropY函数时,都需要关闭定时器
- 在鼠标按下事件down函数中,要关闭定时器,这样当鼠标再次按下的时候,定时器会被关闭;
- X轴方向dropX函数中,判断运动速度值时,要判断绝对值;
- 设置定时器时
setTimeout(processAge(this.dropY,this),30)
,递归dropY函数时,不能直接将this.dropY当成参数传入,若当成参数传入,此时执行的dropY函数中的this就不再是实例this,而是window,所以需要改变this,使其为实例;
- 代码:
- 封装JS代码:
class Drag{ constructor(opt){ opt=opt||{}; if(!opt.ele) return; this.ele=opt.ele; this.disX=null; this.disY=null; this.DOWN=null; this.MOVE=null; this.UP=null; this.maxL=(document.documentElement.clientWidth || document.body.clientWidth)-this.ele.offsetWidth; this.maxT=(document.documentElement.clientHeight || document.body.clientHeight)-this.ele.offsetHeight; this.init(); } init(){ this.DOWN=processAge(this.down,this); on(this.ele,"mousedown",this.DOWN); } down(e){ //通过元素位置计算出光标相对于元素内的位置 disX disY var l=this.ele.offsetLeft; var t=this.ele.offsetTop; var x=e.clientX; var y=e.clientY; this.disX=x-l; this.disY=y-t; this.MOVE=processAge(this.move,this);//此时为一个匿名函数; this.UP=processAge(this.up,this); this.xtimer=this.ytimer=null; //添加事件 if(this.ele.setCapture){//IE浏览器设置焦点捕获 this.ele.setCapture(); on(this.ele,"mousemove",this.MOVE); on(this.ele,"mouseup",this.UP); }else{//标准浏览器下,给document设置事件,阻止默认事件 on(document,"mousemove",this.MOVE); on(document,"mouseup",this.UP); e.preventDefault();//阻止默认事件;防止选中文字; } //点击按下时,关闭定时器; clearTimeout(this.xtimer); clearTimeout(this.ytimer); } move(e){ //设置新位置 var l=e.clientX-this.disX; var t=e.clientY-this.disY; if(l<=0){ l=0; }else if(l>=this.maxL){ l=this.maxL; } if(t<=0){ t=0; }else if(t>=this.maxT){ t=this.maxT; } this.ele.style.left=l+"px"; this.ele.style.top=t+"px"; //弹性运动的数据 if(!this.prevX){ this.prevX=e.clientX; }else{ this.speedX=e.clientX-this.prevX; this.prevX=e.clientX; } } up(){ if(this.ele.releaseCapture){ this.ele.releaseCapture();//释放焦点捕获 off(this.ele,"mousemove",this.MOVE); off(this.ele,"mouseup",this.UP); }else{ off(document,"mousemove",this.MOVE); off(document,"mouseup",this.UP); } this.dropX(); this.dropY(); } dropX(){ clearTimeout(this.xtimer); this.speedX*=0.93; var l=this.ele.offsetLeft+this.speedX; if(l<=0){ l=0; this.speedX*=-1; }else if(l>=this.maxL){ l=this.maxL; this.speedX*=-1; } this.ele.style.left=l+"px"; //判断this.speedX值的绝对值小于0.5后,定时器不再执行; if(Math.abs(this.speedX)>0.5){ this.xtimer=setTimeout(processAge(this.dropX,this),30); } } dropY(){ clearTimeout(this.ytimer); if(!this.speedY){ this.speedY=9.8; }else{ this.speedY+=9; } this.speedY*=0.98;//控制弹跳的频率;值越大,弹跳的频率越多; var t=this.ele.offsetTop+this.speedY; //边界值判断,然后通过stemp开关来控制定时器的开启,物体在底部弹跳时,stemp取值为0,1之间;持续大于maxT值后,会自增;然后大于2; if(t>=this.maxT){ t=this.maxT; this.speedY*=-1; this.stemp++;//当物体在最下面的时候,持续大于maxT值后,会自增;然后大于2; }else{ this.stemp=0; } this.ele.style.top=t+"px"; if(this.stemp<2){ this.ytimer=setTimeout(processAge(this.dropY,this),30); } } }
2.3 订阅式发布体验
- 订阅式发布的本质:
- 需求:一个元素身上创建一个自定义行为,这个行为包括很多方法,当需要执行该元素身上的这个自定义行为时,让所有的跟它有关的方法都执行;
- 实质:
- 给元素ele身上这个自定义行为创建一个自定义属性,这个属性的属性值为一个数组,然后将所有与该行为有关联的方法,插入到这个数组中,避免重复插入;
- 创建一个函数fire,函数中就是为了执行该行为数组中的所有方法;需要使用该行为时,调用fire函数;
- 订阅式发布的思路:
- 创建on函数:给一个元素创建一个自定义行为,然后给这个行为绑定多个不同的方法,然后等待调用执行;
- 创建fire函数:函数内部是执行元素this身上的自定义行为,上绑定的多个方法;
- 与事件库的区别:
- 事件库:是通过给元素在系统事件池中的系统行为绑定一个run函数,在改事件行为触发时,执行run函数,run函数中同样也是执行该行为的所有方法;保证执行函数中的this指向和传入事件对象实参;
- 订阅式发布封装的on函数和fire函数,是给元素创建一个自定义行为,然后给这个自定义行为绑定多个不同方法,然后通过fire函数的调用,来执行这些方法;保证执行函数中的this指向和传入事件对象实参;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>订阅式发布体验</title> <style> div{ width: 200px; height: 200px; background-color: red; } </style> </head> <body> <div id="div"></div> <script> var oDiv=document.getElementById("div"); //封装一个on函数,给元素的一个行为绑定不同的方法; //on函数的目的:给元素身上创建一个自定义属性,赋值为数组,作为元素自己的事件池,然后将每个方法push进事件池数组中;避免重复放入; function on(ele,type,fn){ //思路:元素创建一个自定义属性,作为自己的事件池,里面插入方法fn if(!ele["on"+type]){ ele["on"+type]=[]; } var a=ele["on"+type]; if(a.length){ //去重 for(var i=0; i<a.length; i++){ if(a[i]===fn) return; } } //把fn都放在事件池中 a.push(fn); } //发射器fire函数:目的是监听行为,当需要执行type行为的时候,让所有的方法执行 //fire函数的目的:待执行,当需要使用该元素身上的自定义行为时,就调用执行函数,将自己事件池上的方法调用执行,保证方法函数中的this指向为该元素和传入实参e; //注意:调用时,必须保证函数中的this指向为该元素; function fire(type,e){ //保证函数中的this为元素 var a=this["on"+type]; if(a.length){ for(var i=0; i<a.length; i++){ a[i].call(this,e);//保证了函数在执行时,里面的this指向为元素,并传入实参e; } } } on(oDiv,"meihao",fn1); on(oDiv,"meihao",fn2); on(oDiv,"meihao",fn3); on(oDiv,"meihao",fn4); function fn1() { console.log("喜欢"); } function fn2() { console.log("相濡以沫"); } function fn3(e) { console.log("共同努力"); console.log(e.type) } function fn4() { console.log(this); console.log("相互关心爱护"); } //需求:给oDiv添加点击事件,当点击事件触发时,执行meihao行为上所有的方法; oDiv.onclick=function (e) { e=e||window.event; fire.call(this,"meihao",e); } </script> </body> </html>
3 订阅式发布版与继承版弹性运动拖拽实例
- 继承与订阅发布的区别:
- 继承:属于开发人员自己进行版本升级,不同的功能创建不同的类函数;
- 订阅发布:针对的是用户,即:给用户留好升级的接口,如果用户想要扩充功能,用户自己添加;
- 订阅发布函数EventEmitter
- 目的:给实例对象this创建一个自定义属性,属性值为一个数组,然后将自定义行为上的方法插入到数组中;再创建一个函数fire,用于执行数组中的方法;
- 注意点:
- on函数中的this都指的是实例对象,添加返回值,返回实例this,用于链式操作;
- fire函数中获取数组时,由于on函数在正常情况下,不会绑定,所以不会创建数组,则拿到的a为undefined,会报错,所以为了防止报错,必须设置当数组不存在时,让其为空数组;
- 在执行数组内绑定的方法时,要保证函数中的this为实例对象,和传入事件对象实参;
- on和fire函数调用的时候,都是需要实例对象调用;
- fire函数在每个需要接口的地方调用,当需要添加功能时,就给指定行为用on绑定行为;然后就会执行;
- on函数在使用时,用实例对象使用,并且可以进行链式操作;
- 代码:
class EventEmitter{ constructor(){} //on函数方法,给实例this创建一个自定义行为,给这个行为绑定多个方法 on(type,fn){ if(!this[type]){ this[type]=[]; } var a=this[type]; if(a.length){ for(var i=0; i<a.length; i++){ if(a[i]===fn) return; } } a.push(fn); return this;//用于链式操作; } fire(type,e){ var a=this[type] || [];//需注意,如果不绑定的话,a不存在,会报错,所以给a赋值为空数组; if(a.length){ for(var i=0; i<a.length; i++){ a[i].call(this,e);//保证函数中的this为实例对象; } } } }
- 订阅式发布版拖拽实例
- 本质:将实例drag继承EventEmitter的属性方法;然后在想要扩展功能的地方调用fire函数,用做接口,当需要扩展功能时,用on添加方法,然后自动添加方法执行,不会改变原来的函数类的代码功能;
- 不会改变原来的函数类,只需重新建立另一个实例,就可调动;
- 将纯净的拖拽转变为弹性运动功能的拖拽的思路:
- 在down,move,up三个函数中,添加接口
this.fire("myMove",e)
,即给实例对象添加了一个myMove行为,相当于发布上,如果没有给myMove用on来绑定方法,就不执行,当用on绑定方法后,就会立刻执行;可以给同一行为绑定多个方法; - 在使用时,创建实例对象,然后给实例对象用on给其myMove的行为绑定多个方法,然后到达弹性运动的效果;在用on绑定时,可以用链式操作;
- 在down,move,up三个函数中,添加接口
- 代码:
- 执行代码:
<script> var oDiv=document.getElementById("div1"); var drag=new Drag({ ele:oDiv }); //想要进行链式操作,必须保证on函数中返回的值为实例对象; drag.on("myDown",closeTimer).on("myMove",fn1).on("myUp",dropX).on("myUp",dropY); function closeTimer() { clearTimeout(this.xtimer); clearTimeout(this.ytimer); } function fn1(e) { //获取X轴方向上的speedX if(!this.prevX){ this.prevX=e.clientX; }else{ this.speedX=e.clientX-this.prevX; this.prevX=e.clientX; } } function dropX(e) { clearTimeout(this.xtimer); this.speedX*=0.93; var l=this.ele.offsetLeft+this.speedX; if(l<=0){ l=0; this.speedX*=-1; }else if(l>=this.maxL){ l=this.maxL; this.speedX*=-1; } this.ele.style.left=l+"px"; if(Math.abs(this.speedX)>0.5){ this.xtimer=setTimeout(processAge(arguments.callee,this),30);//注意定时器中的函数调用时,函数中this为window; } } function dropY(e) { clearTimeout(this.ytimer); if(!this.speedY){ this.speedY=9.8 }else{ this.speedY+=9.8; } this.speedY*=0.98; var t=this.ele.offsetTop+this.speedY; if(t>=this.maxT){ t=this.maxT; this.speedY*=-1; this.stmp++; }else{ this.stmp=0; } this.ele.style.top=t+"px"; if(this.stmp<2){ this.ytimer=setTimeout(processAge(arguments.callee,this),30); } } </script>
- JS代码:
//订阅式发布类函数 class EventEmitter{ constructor(){} //on函数方法,给实例this创建一个自定义行为,给这个行为绑定多个方法 on(type,fn){ if(!this[type]){ this[type]=[]; } var a=this[type]; if(a.length){ for(var i=0; i<a.length; i++){ if(a[i]===fn) return; } } a.push(fn); return this;//用于链式操作; } fire(type,e){ var a=this[type] || [];//需注意,如果不绑定的话,a不存在,会报错,所以给a赋值为空数组; if(a.length){ for(var i=0; i<a.length; i++){ a[i].call(this,e);//保证函数中的this为实例对象; } } } } class Drag extends EventEmitter{ constructor(opt){ super(); opt=opt||{}; if(!opt.ele) return; this.ele=opt.ele; this.disX=null; this.disY=null; this.maxL=null; this.maxT=null; this.DOWN=null; this.MOVE=null; this.UP=null; this.init(); } init(){ this.DOWN=processAge(this.down,this); on(this.ele,"mousedown",this.DOWN); } down(e){ //通过元素位置计算出光标相对于元素内的位置 disX disY var l=this.ele.offsetLeft; var t=this.ele.offsetTop; var x=e.clientX; var y=e.clientY; this.disX=x-l; this.disY=y-t; this.MOVE=processAge(this.move,this);//此时为一个匿名函数; this.UP=processAge(this.up,this); //添加事件 if(this.ele.setCapture){//IE浏览器设置焦点捕获 this.ele.setCapture(); on(this.ele,"mousemove",this.MOVE); on(this.ele,"mouseup",this.UP); }else{//标准浏览器下,给document设置事件,阻止默认事件 on(document,"mousemove",this.MOVE); on(document,"mouseup",this.UP); e.preventDefault();//阻止默认事件;防止选中文字; } //关闭定时器 this.fire("myDown",e); } move(e){ //边界值判断 var l=e.clientX-this.disX; var t=e.clientY-this.disY; this.maxL=(document.documentElement.clientWidth || document.body.clientWidth)-this.ele.offsetWidth; this.maxT=(document.documentElement.clientHeight || document.body.clientHeight)-this.ele.offsetHeight; if(l<=0){ l=0; }else if(l>=this.maxL){ l=this.maxL; } if(t<=0){ t=0; }else if(t>=this.maxT){ t=this.maxT; } //设置新位置 this.ele.style.left=l+"px"; this.ele.style.top=t+"px"; //获取X轴方向上的运动速度 this.fire("myMove",e); } up(e){ if(this.ele.releaseCapture){ this.ele.releaseCapture();//释放焦点捕获 off(this.ele,"mousemove",this.MOVE); off(this.ele,"mouseup",this.UP); }else{ off(document,"mousemove",this.MOVE); off(document,"mouseup",this.UP); } //执行两个函数 this.fire("myUp",e); } }
- 继承版拖拽实例
- 思路:继承纯净版类函数Drag,然后在新的类函数中,创建函数,绑定事件,然后执行函数;
- 使用时:两个类独立开来,使用哪个功能,创建哪个类的实例对象;
- 注意点:给事件行为mousemove绑定方法时,需要根据浏览器种类,给不同的元素绑定事件行为的方法;
- 代码:
- 执行代码:
<script> var oDiv=document.getElementById("div1"); //1 纯净版拖拽,创建Drag实例对象; var drag=new Drag({ ele:oDiv }); //2 弹性运动版拖拽,创建Elastic实例对象 var drag1=new Elastic({ ele:oDiv }) </script>
- JS代码:
class Drag{ constructor(opt){ opt=opt||{}; if(!opt.ele) return; this.ele=opt.ele; this.disX=null; this.disY=null; this.maxL=null; this.maxT=null; this.DOWN=null; this.MOVE=null; this.UP=null; this.init(); } init(){ this.DOWN=processAge(this.down,this); on(this.ele,"mousedown",this.DOWN); } down(e){ //通过元素位置计算出光标相对于元素内的位置 disX disY var l=this.ele.offsetLeft; var t=this.ele.offsetTop; var x=e.clientX; var y=e.clientY; this.disX=x-l; this.disY=y-t; this.MOVE=processAge(this.move,this);//此时为一个匿名函数; this.UP=processAge(this.up,this); //添加事件 if(this.ele.setCapture){//IE浏览器设置焦点捕获 this.ele.setCapture(); on(this.ele,"mousemove",this.MOVE); on(this.ele,"mouseup",this.UP); }else{//标准浏览器下,给document设置事件,阻止默认事件 on(document,"mousemove",this.MOVE); on(document,"mouseup",this.UP); e.preventDefault();//阻止默认事件;防止选中文字; } } move(e){ //边界值判断 var l=e.clientX-this.disX; var t=e.clientY-this.disY; this.maxL=(document.documentElement.clientWidth || document.body.clientWidth)-this.ele.offsetWidth; this.maxT=(document.documentElement.clientHeight || document.body.clientHeight)-this.ele.offsetHeight; if(l<=0){ l=0; }else if(l>=this.maxL){ l=this.maxL; } if(t<=0){ t=0; }else if(t>=this.maxT){ t=this.maxT; } //设置新位置 this.ele.style.left=l+"px"; this.ele.style.top=t+"px"; } up(){ if(this.ele.releaseCapture){ this.ele.releaseCapture();//释放焦点捕获 off(this.ele,"mousemove",this.MOVE); off(this.ele,"mouseup",this.UP); }else{ off(document,"mousemove",this.MOVE); off(document,"mouseup",this.UP); } } } class Elastic extends Drag{ constructor(opt){ super(opt); this.speedX=null; this.speedY=null; this.stemp=null; this.xtimer=this.ytimer=null; this.DOWN2=processAge(this.down2,this); this.MOVE2=processAge(this.move2,this); this.UP2=processAge(this.up2,this); this.init2(); } init2(){ on(this.ele,"mousedown",this.DOWN2); } down2(){ if(this.ele.setCapture){//IE浏览器设置焦点捕获 on(this.ele,"mousemove",this.MOVE2); on(this.ele,"mouseup",this.UP2); }else{//标准浏览器下,给document设置事件,阻止默认事件 on(document,"mousemove",this.MOVE2); on(document,"mouseup",this.UP2); } clearTimeout(this.xtimer); clearTimeout(this.ytimer); } move2(e){ if(!this.prevX){ this.prevX=e.clientX; }else{ this.speedX=e.clientX-this.prevX; this.prevX=e.clientX; } } up2(){ this.dropX(); this.dropY(); if(this.ele.setCapture){//IE浏览器设置焦点捕获 off(this.ele,"mousemove",this.MOVE2); off(this.ele,"mouseup",this.UP2); }else{//标准浏览器下,给document设置事件,阻止默认事件 off(document,"mousemove",this.MOVE2); off(document,"mouseup",this.UP2); } } dropX(){ clearTimeout(this.xtimer); this.speedX*=0.93; var l=this.ele.offsetLeft+this.speedX; if(l<=0){ l=0; this.speedX*=-1; }else if(l>=this.maxL){ l=this.maxL; this.speedX*=-1; } this.ele.style.left=l+"px"; //判断this.speedX值的绝对值小于0.5后,定时器不再执行; if(Math.abs(this.speedX)>0.5){ this.xtimer=setTimeout(processAge(this.dropX,this),30); } } dropY(){ clearTimeout(this.ytimer); if(!this.speedY){ this.speedY=9.8; }else{ this.speedY+=9.8; } this.speedY*=0.93;//控制弹跳的频率;值越大,弹跳的频率越多; var t=this.ele.offsetTop+this.speedY; //边界值判断,然后通过stemp开关来控制定时器的开启,物体在底部弹跳时,stemp取值为0,1之间;持续大于maxT值后,会自增;然后大于2; if(t>=this.maxT){ t=this.maxT; this.speedY*=-1; this.stemp++;//当物体在最下面的时候,持续大于maxT值后,会自增;然后大于2; }else{ this.stemp=0; } this.ele.style.top=t+"px"; if(this.stemp<2){ this.ytimer=setTimeout(processAge(this.dropY,this),30); } } }