javascript中的事件基础以及核心原理以及项目实战 1、事件基础:全新认识事件
1、什么是事件? 事件是元素天生具备的行为方式 (和写不写js代码没关系),当我们去操作元素的时候会触发元素很多事件
2、事件绑定 给当前元素的某一个事件绑定方法,目的是为了让当前元素某个事件被触发的时候,可以做一些事情
给一个事件绑定方法,目前有两种方式: 1、DOM0级事件绑定oBox.onclick=function(){}
2、DOM2级事件绑定oBox.addEventListener('click', function(){}, false)
标准浏览器oBox.attachEvent('onclick', function()})
IE6-8
3、常用的事件汇总
【PC端】
表单元素常用事件行为 blur: 失去焦点 focus: 获取焦点 change: 内容改变 select: 被选中
键盘常用事件 keydown: 键盘按下 keyup: 键盘抬起 keypress: 键盘按下后有keypress(中文输入法状态下,会触发keydown,但是由于内容没有改变,keypress会一直触发)
鼠标常用事件 click: 点击(不是单机) dblclick: 双击(300ms内凉席触发量词点击事件,这样为双击事件) mouseover: 鼠标划过 mouseout: 鼠标划开 mouseenter: 鼠标进入 mouseleave: 鼠标离开 mousemove: 鼠标移动 mousedown: 鼠标左键按下 mouseup: 鼠标左键抬起 mousewheel: 鼠标滚轮滚动
其他事件行为 load: 加载成功 error: 加载失败 scroll: 滚动条滚动时间 resize: 大小改变事件 window.onresize当浏览器的窗口大小发生改变触发这个事件
…
【移动端】
移动端键盘一般都是虚拟键盘,虽然部分手机存在keydown/keyup但是兼容不好,所以我们想用键盘事件的时候,使用input事件代替inputBox.oninput = function(){}
移动端没有鼠标,所以鼠标类的事件在移动端兼容性都特别差
移动端的大部分操作是靠手指完成,移动端独有手指事件 单手指事件模型: touchstart、touchmove、touchend、touchcancel(由于意外事件导致触摸事件结束)
多手指事件模型: gesturestart、gesturechange、gestureend…
移动端还有很多操作是基于手机硬件完成的,例如手机传感器,手机陀螺仪,手机重力感应器等
在移动端兼容click事件,PC端的click是点击,但是移动端把click事件当做单击: 移动端使用click事件处理点击操作存在300ms
事件对象 1 2 3 4 oBox.onclick = function (e ) { }
当元素的某一个事件被触发,不仅会把之前绑定的方法执行,而且还会给当前绑定的方法传递一个值(浏览器默认传递),我们把传递的这个值成为事件对象
1、因为这个值是个对象类型的值,里面存储了很多的属性和方法 2、这个对象中存储的值都是当前操作的一些基本信息,例如:鼠标的位置、触发的行为类型、触发的事件源等
以上所说的都是针对标准浏览器,IE6-8下不是这样的机制
IE6-8方法被触发执行的时候,浏览器并没有把事件对象当做值传递给函数(e在IE6-8下是undefined);但是IE6-8也有事件对象、事件对象需要我们通过window.event单独获取
事件对象是为了记录当前操作基本信息的,所以只和本次操作有关:本次操作,页面中不管通过什么方式获取的e或者window.event(也不关在哪获取),他们存储的基本信息是相同的
鼠标事件对象 MouseEvent
clientX / clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴的坐标 pageX / pageY:当前鼠标触发点距离BODY左上角的X/Y轴坐标(页面第一屏左上角),但是IE6-8中没有这两个属性
type:当前触发的事件类型
target:事件源(当前鼠标操作的是哪一个元素,那么事件源就是谁),IE6-8下没有tarrget属性,它有的是srcElement
这个属性代表事件源
preventDefault:此方法是为了阻止事件的默认行为,IE6-8下没有这个方法,需要使用e.returnValue = false
处理
stopPropagation:此方法是为了阻止事件的冒泡传播,IE6-8不兼容使用cancelBubble
处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 oBox.onclick = function (e ) { if (typeof e === 'undefined' ){ e = window .event; e.pageX = e.clientX + (document .documentElement.scrollLeft || document .body.scrollLeft); e.pageY = e.clientY + (document .documentElement.scrollTop || document .body.scrollTop); e.target = e.srcElement; e.preventDefault = function ( ) { e.returnValue = false ; } e.stopPropagation = function ( ) { e.cancelBubble = true ; } } }
上面的兼容处理方式属于比较完整的,但是如果项目中我们指向用到一个不兼容的属性,哦们没有必要写这么多,简单处理一下兼容就可以了
1 2 3 4 5 oBox.onclick = function (e ) { e = e || window .event; e.target = e.target || e.scrElement; e.preventDefault?e.preventDefault:e.returnValue = false ; }
键盘事件对象 KeyboardEvent
code:当前键盘的按键,例如:按删除键,存储的是Backspace
(IE6-8下没有这个属性),还有一个叫key的属性和code一样,存储的也是按键的名称
keyCode:存储的是当前键盘对应的码值(大部分按键都有自己的码值)
which:和keyCode一样对应的也是键盘码的值(不兼容IE6-8)
1 2 3 4 inputBox.onkeyup = function (e ) { e = e || window .event; var code = e.which || e.keyCode; }
移动端手指事件对象 TouchEvent
touches / changeTouches / targetTouches: 存储的是当前屏幕上每一个手指操作的位置信息(手指离开屏幕没有相关信息,这样touchend事件中我们无法通过touches获取手指信息)
changedTouches:手指再屏幕上的时候,和touches获取的信息一样,但是它可以记录手指离开屏幕一瞬间所在的位置信息(最常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 TouchEvent type: 'touchstart', target: 事件源 touches: 0: { clientX: xxx, clientY: xxx, pageX: xxx, pageY: xxx, ... } length: 1 changeTouches以及targetTouches存储的结构和touches相同
我们知道移动端的click是点击事件(不是pc端的点击效果),存在300ms的延迟,项目中我们需要解决这个延迟: 使用touchstart/touchmove/touchend来处理
简单处理
1 2 3 4 5 oBox.ontouchend = function ( ) { }
详细处理,解决移动端click300ms延迟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 var box = document .querySelector('#box' );box.ontouchstart = function (e ) { let point = e.changedTouches[0 ]; this .strX = point.pageX; this .strY = point.pageY; this .isMove = false ; }; box.ontouchmove = function (e ) { let point = e.changedTouches[0 ]; let changeX = point.pageX - this .strX, changeY= point.pageY - this .strY; this .changeX = changeX; this .changeY = changeY; if (Math .abs(changeX) > 10 || Math .abs(changeY) > 10 ){ this .isMove = true ; } }; box.ontouchend = function (e ) { let point = e.changedTouches[0 ]; if (!this .isMove){ console .log('这是点击操作' ); } let dir = null ; if (Math .abs(this .changeX) > Math .abs(this .changeY)){ dir = this .changeX < 0 ? 'Left' : 'Right' ; }else { dir = this .changeY < 0 ? 'Top' : 'Down' ; } console .log(`当前手指滑动的方向为:${dir} ` ); }
在移动端开发中,我们需要的一些操作(例如:点击,单机,双击,长按,滑动[四个方向]…)都是基于内置原生的touchstart/touchmove/touchend事件一点点模拟出来的效果,没有现成的事件 而多手指操作(例如:旋转、缩放…)都是基于gesture事件模型模拟出来的效果
目前市场上有很多成熟的类库或插件,专门为大家把常用的操作进行了封装,我们直接调取使用即可
1、fastclick.js 目的就是为了解决移动端click事件300ms延迟问题(如果我们的移动端使用了click我们只需要把这个js导入配置一下即可) 2、百度云touch手势事件库 3、hammer.js 4、zepto.js 提供了移动端事件操作的板块,也是目前市场上使用率最高的(小型JQ)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $('.box' ).tap(function ( ) { }); $('.box' ).singleTap(function ( ) { }); $('.box' ).doubleTap(function ( ) { }); $('.box' ).longTap(function ( ) { }); $('.box' ).swipe(function ( ) { });
A标签的默认行为以及阻止
A标签都有那些默认行为 1、超链接:点击A标签可以实现页面的跳转 2、锚点定位:通过hash(哈希)值定位到当前页面指定ID元素的位置
真实项目中我们项用A标签做一个普通的按钮(优势:它的hover样式是兼容所有浏览器的)
锚点定位(哈希定位):定位到当前页面中和hash值(#xxx)相同的元素位置 1、首先在URL地址栏末尾追加一个hash值 2、如果地址栏当中有hash值,浏览器除了页面渲染外,在渲染完成后会默认定位到指定hash值元素位置
阻止A标签的默认行为
1 2 3 //=> 在html中阻止默认行为(最常用) <a href="javascript:;"></a> <a href="javascript:void 0;"></a>
1 2 3 4 5 6 7 8 9 10 11 12 link.onclick = function ( ) { return false ; } link.onclick = function (e ) { e = e || window .event; e.preventDefault?e.preventDefault():e.returnValue=false ; }
事件的传播机制
事件传播有三个阶段 Event.prototype
0 NONE: 默认值,不代表任何的意思
1 CAPTURING_PHASE: 捕获阶段
2 AT_TARGET: 目标阶段(当前事件源)
3 BUBBLING_PHASE: 冒泡阶段
点击某个元素,触发click事件 浏览器在执行元素上绑定的click事件之前 1、从整个页面的document开始向内查找,把元素的祖先元素遍历一遍(为冒泡阶段的传播途径做准备) 2、接下来找到目标元素(找到事件源)把事件源上绑定的方法执行(没有绑定方法就不执行) 3、不仅触发了当前事件源的点击行为,而且浏览器会按照第一阶段规划的传播路线,从内向外把祖先元素的click行为依次触发
当前元素的某个事件行为被触发,它所有的祖先元素(一直到document)的相关事件行为也会被触发(顺序是从内向外),如果组件元素的这个行为绑定了方法,绑定的方法也会被触发,我们把事件的这种传播机制叫做**冒泡传播**
1 2 3 xxx.addEventListener('click' , function ( ) {}, true ); xxx.addEventListener('click' , function ( ) {}, false );
mouseover和mouseenter的区别
mouseover:鼠标滑到元素上 mouseenter:鼠标进入元素里面
【1】 mouseover存在事件的冒泡传播机制,而mouseenter浏览器把它的事件传播机制阻止了
【2】 鼠标从父元素进入到子元素 mouseover:先触发父元素的mouseout(因为鼠标已经不再父元素上了,mouseover本意是鼠标在元素上菜算触发),在触发子元素的mouseover(由于冒泡传播机制,导致父元素的mouseover也被重新触发了)
mouseenter:进入,从大盒子进入到小盒子,没有触发大盒子的mouseleave事件,但是也出发了小盒子的mouseenter,浏览器阻止了它的冒泡传播,所大盒子的mouseenter不会被触发
事件委托
利用事件的冒泡传播机制完成(mouseenteru存在委托机制,因为它不存在冒泡传播)
一个容器中的很多元素的同一个事件都要绑定方法,此时我们没有必要在获取所有元素,一个个的绑定方法,我们只需要给最外层元素的这个事件绑定一个方法,这样不管里面哪一个元素的这个事件行为被触发都会利用冒泡传播机制,把最外层容器绑定的那个方法执行,在方法执行的时候,我们可以根据事件源判断出操作的是那个元素,从而做不同的事件(使用事件委托这样完成的操作比一个个的单独事件绑定性能提高50%左右)
DOM2级别事件绑定原理以及兼容处理 初步了解JS中事件绑定的方式
DOM0事件绑定1 2 3 4 5 6 oBox.onclick = function (e ) { e = e || window .event; } oBox.onmouseenter = function ( ) {} ...
DOM2事件绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 oBox.addEventListener('click' , function (e ) { }, false ); oBox.attachEvent('onclick' , function (e ) { });
有DOM0和DOM2事件绑定,那么DOM1事件绑定呢? 因为在DOM第一代升级迭代的时候,DOM元素的事件绑定方式沿用的是DOM0代绑定的方式,在此版本DOM中,事件绑定没有升级
DOM0事件绑定和DOM2事件绑定的区别
DOM0事件绑定的原理
1、给当前元素对象的某一个私有属性(onxxx)赋值的过程(之前属性默认值是null,如果我们给赋值一个函数,相当于绑定了一个方法)
2、当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的函数建立关联,以及建立DOM元素行为操作的监听,当某一个行为被用户触发,浏览器会把相关行为赋值的函数执行
特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 document .body.onclick=function ( ) {}; document .body.onsmile=function ( ) {}; document .body.onclick=null ;document .body.onclick=function ( ) { console .log(1 ); } document .body.onclick=function ( ) { console .log(2 ); }
DOM2事件绑定的原理
1、我们DOM2事件绑定使用的addEventListener/attachEvent都是在EventTarget这个内置类的原型上定义的,我们调取使用的时候,首先通过原型链找到这个方法,然后执行完成事件绑定的效果
2、浏览器首先会给当前元素的某一个时间行为开辟一个事件池(时间队列)[其实是浏览器有一个统一的事件池,我们每个元素的某个行为绑定的方法都放在这个事件池中,只是通过相关的标识来区分的]
3、当元素的某一个行为触发,浏览器会到事件池中,把当前存放在事件池中的所有方法,依次按照存放的先后顺序执行
特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 DOMContentLoaded... document .body.DOMContentLoaded === undefined ; window .addEventListener('DOMContentLoaded' , function ( ) { }, false ); window .attachEvent('onDOMContentLoaded' , function ( ) { }); function fn1 ( ) { console .log(1 ); } function fn2 ( ) { console .log(2 ); } function fn3 ( ) { console .log(3 ); } document .body.addEventListener('click' , fn1, false );document .body.addEventListener('click' , fn3, false );document .body.addEventListener('click' , fn2, false );document .body.addEventListener('click' , fn3, false ); document .body.removeEventListener('click' , fn2, false );document .body.addEventListener('click' , function ( ) { console .log(1 ); }, false ); document .body.removeEventListener('click' , function ( ) { console .log(1 ); }, false );
1 2 3 4 5 document .body.onclick = function fn ( ) { console .log(fn); console .log(1 ); } fn();
1 2 3 4 5 6 7 8 document .addEventListener('click' , function fn ( ) { console .log(1 ); }, false ); document .addEventListener('click' , function fn ( ) { console .log(2 ); }, false );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fn ( ) { console .log(1 ); } document .addEventListener('click' , fn, false );document .addEventListener('click' , fn, false ); document .onclick = fn; document .onclick = function ( ) { console .log(2 ); }; document .addEventListener('click' , function ( ) { console .log(3 ); }, false );
事件是元素天生自带的行为 click mouseover DOMContentLoaded… DOM0:浏览器会把一些常用的事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定
DOM2:凡事浏览器给元素天生设置的事件,在DOM2中都可以使用
window.onload和$(document).ready()的区别
window.onload: 当浏览器中所有的资源内容(DOM结构、文本内容、图片…)都加载完成,触发load事件 1、基于DOM0事件绑定完成的,所以在同一个页面中只能给它绑定一个方法(绑定多个以最后一次绑定的为主)
2、想在一个页面中使用多次,我们应该是基于DOM2事件绑定的
1 2 3 4 5 6 7 8 function fn1 ( ) { } function fn2 ( ) { } window .addEventListener('load' , fn1, false );window .addEventListener('load' , fn2, false );
$(function(){})或者$(document).ready(function(){}) 当文档中的DOM结构加载完成就会被触发执行,而且在同一个页面当中可以使用多次
1、JQ中提供的方法,JQ是基于DOMContentLoaded这个事件完成这个操作的
2、JQ中的事件绑定都是基于DOM2事件绑定完成的
3、但是DOMContentLoaded在IE6-8下使用attachEvent也是不支持的,JQ在IE6-8下使用的是onreadystatechange这个事件处理的
DOM2事件绑定的兼容处理
语法上的兼容 [标准] curEle.addEventListener(‘type’, fn, false); [IE6-8] curEle.attachEvent(‘ontype’, fn);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 let on = function (curEle, type, fn ) { if (document .addEventListener) { curEle.addEventListener(type, fn, false ); return ; } curEle.attachEvent(`on${type} ` , fn); }; let off = function (curEle, type, fn ) { if (document .addEventListener) { curEle.removeEventListener(type, fn, false ); return ; } curEle.detachEvent(`on${type} ` , fn); }
除了语法上的区别,在执行机制上有一些区别 在IE6-8中使用attachEvent做事件绑定(把方法存放在当前元素指定事件类型的事件池中)
1、顺序问题:当事件行为触发,执行对应事件池中存放的方法的时候,IE低版本浏览器执行方法的顺序是乱序(标准浏览器是按照绑定的先后顺序一次执行的)
2、重复问题:IE低版本浏览器在向事件池中增加方法的时候,没有去重机制,哪怕当前方法已经存放过了,还会重复的添加进去(标准浏览器事件的事件池机制很完善,可以自动去重,已经存放过的方法不允许再添加进来)
3、this问题:IE低版本浏览器中,当事件行为触发,把事件池中方法执行,此时方法中的this指向window,而不是像标准浏览器一样,指向当前元素本身
究其根本:其实都是IE低版本浏览器对其它内置事件池机制的不完善导致的
DOM2事件绑定兼容处理的原理:告别LOW的IE6-8的内置事件池,而是自己创建一个类似标砖浏览器的“自定义事件池”标砖浏览器不需要处理兼容,只有IE6-8才需要处理兼容
自定义兼容事件池
所谓事件池其实就是一个容器,不管是内置的还是自己创建的 IE6-8兼容处理:
1、 on: 没有自定义的事件池,我们需要手动创建一个自定义的事件池把需要绑定的方法,全部存放在自定义的事件池中 在存储的时候我们自己做重复验证,有的话就不在存储了(重复问题就解决了)
2、 off: 把不需要绑定的方法从自定义的事件池中移除掉
3、 run: 我们需要把run放在内置事件池中,当行为触发,需要浏览器把run方法执行,在run中把自定义事件池中存放的所有方法执行 this问题 -> 顺序问题 都解决了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 var on = function (curEle, type, fn ) { if (document .addEventListener) { curEle.addEventListener(type, fn, false ); return ; } if (typeof curEle[type + 'Pond' ] === 'undefined' ) { curEle[type + 'Pond' ] = []; curEle.attachEvent('on' + type, run.bind(curEle)) } var ary = curEle[type + 'Pond' ]; for (var i = 0 ; i < ary.length; i++){ if (ary[i] === fn){ return ; } } ary.push(fn); }; var off = function (curEle, type, fn ) { if (document .removeEventListener) { curEle.removeEventListener(type, fn, false ); return ; } var ary = curEle[type + 'Pond' ]; if (ary) { for (var i = 0 ; i < ary.length; i++){ if (ary[i] === fn){ ary[i] = null ; break ; } } } } var run = function (e ) { if (type e.target === 'undefined' ) { e.target = e.srcElement; e.which = e.keyCode; e.pageX = e.clientX + (document .documentElement.scrollLeft || document .body.scrollLeft); e.pageY = e.clientY + (document .documentElement.scrollTop || document .body.scrollTop); e.preventDefault = function ( ) { e.returnValue = false ; } e.stopPropagation = function ( ) { e.cancelBubble = true ; } } var ary = this [e.type + 'Pond' ]; if (ary){ for (var i = 0 ; i < ary.length; i++){ var item = ary[i]; if (item === null ) { ary.splice(i, 1 ); i--; continue ; } item.call(this , e); } } }
bind方法的兼容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function fn (e ) { console .log(e, this .tag.Name); } fn.call(obj, e); } Function .prototype.myBind = function myBind (context ) { context = context || window ; var outerArg = Array .prototypr.slice.call(arguments , 1 ); var _this = this ; if ('bind' in Function .prototype) { outerArg.unshift(context); return _this.bind.apply(_this, outerArg); } return function (e ) { var innerArg = Array .prototype.slice.call(arguments , 0 ); outerArg = outerArg.concat(innerArg); _this.apply(context, outerArg); } }; fn.myBind(obj, 100 , 200 ); document .body.onclick = fn.myBind(obj, 100 , 200 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 ~function ( ) { Function .prototype.myBind = function (context=window, ...outer ) { if ('bind' in this ) { return this .bind(...arguments); } return (...inner ) => this .apply(context, outer.concat(inner)) }; let on = function (curEle, type, fn ) { if (document .addEventListener) { curEle.addEventListener(type, fn, false ); return ; } if (typeof curEle['pond' + type] === 'undefined' ) { curEle['pond' + type] = []; curEle.attachhEvent('on' + type, run.myBind(curEle)); } let pondAry = curEle['pond' + type]; for (let i = 0 ; i < pondAry.length; i++) { if (pondAry[i] === fn) { return ; } } pondAry.push(fn); }; let off = function (curEle, type, fn ) { if (document .removeEventListener) { curEle.removeEventListener(type, fn, false ); return ; } let pondAry = curEle['pond' + type]; if (!pondAry) return ; for (let i = 0 ; i < pondAry.length; i++) { let itemFn = pondAry[i]; if (itemFn === fn) { pondAry[i] = null ; break ; } } }; let run = function (e ) { e = e || window .event; if (!e.target) { e.target = e.srcElement; e.pageX = e.clientX + document .documentElement.scrollLeft || document .body.scrollLeft; e.pageY = e.clientY + document .documentElement.scrollTop || document .body.scrollTop; e.which = e.keyCode; e.preventDefault = function ( ) { e.returnValue = false ; }; e.stopPropagation = function ( ) { e.cancelBubble = true ; }; } let pondAry = this ['pond' + e.type]; if (!pondAry) return ; for (let i = 0 ; i < pondAry.length; i++) { let itemFn = pondAry[i]; if (itemFn === null ) { pondAry.splice(i, 1 ); i--; coontinue; } itemFn.call(this , e); } }; window .$event = { on: on, off: off } }()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 let down = function (e ) { this .strX = e.clientX; this .strY = e.clientY; this .strL = this .offsetLeft; this .strT = this .offsetTop; this ._MOVE = move.myBind(this ); this ._UP = up.myBind(this ); $event.on(document , 'mousemove' , this ._MOVE); $event.on(document , 'mouseup' , this ._UP); clearInterval(this .timerDrop); clearInterval(this .timerFly); }; let move = function (e ) { let curL = e.clientX - this .strX + this .strL, curT = e.clientY - this .strY + this .strT; curL = curL < 0 ? 0 : (curL > maxL ? maxL : curL); curT = curT < 0 ? 0 : (curT > maxT ? maxT : curT); this .style.left = curL + 'px' ; this .style.top = curT + 'px' ; if (!this .preFly) { this .preFly = this .offsetLeft; } else { this .speedFly = this .offsetLeft - this .preFly; this .preFly = this .offsetLeft } }; let up = function (e ) { $event.off(document , 'mousemove' , this ._MOVE); $event.off(document , 'mouseup' , this ._UP); moveFly.call(this ); moveDrop.call(this ); }; let moveFly = function ( ) { let speedFly = this .speedFly; this .timerFly = setInterval(() => { if (Math .abs(speedFly) < 0.5 ) { clearInterval(this .timerFly); return ; } speedFly *= 0.98 ; let curL = this .offsetLeft + speedFly; if (curL > maxL || curL < 0 ) { speedFly *= -1 ; curL = curL > maxL ? maxL : (curL < 0 ? 0 : null ); } this .style.left = curL + 'px' ; }, 17 ); } let moveDrop = function ( ) { let speedDrop = 10 , flagDrop = 0 ; this .timerDrop = setInterval(() => { if (flagDrop >= 2 ) { clearInterval(this .timerDrop); return ; } speedDrop *= 0.98 ; speedDrop += 9.8 ; let curT = this .offsetTop + speedDrop; if (curT > maxT) { speedDrop *= -1 ; curT = maxT; flagDrop++; } else { flagDrop = 0 ; } this .style.top = curT + 'px' ; }, 17 ); } let oBox = document .getElementById('box' ), maxL = (document .documentElement.clientWidth || document .body.clientWidth) - oBox.offsetWidth, maxT = (document .documentElement.clientHeight || document .body.clientHeight) - oBox.offsetHeight; $event.on(oBox, 'mousedown' , down);