DOM0事件和DOM2事件模型 —— 1、事件基础:事件对象、事件委托(事件代理)...

1、事件基础及移动端和PC端常用事件汇总

js中的dom0事件和dom2事件

事件基础知识

  • 什么是事件?什么是事件绑定?
  • PC端常用的事件?
  • 移动端常用的事件?

事件对象

  • 什么是事件对象
  • 鼠标事件对象
  • 键盘事件对象
  • 手指事件对象
  • 其它事件对象
  • 事件对象兼容处理?

阻止事件的默认行为

  • A标签的默认行为及阻止
  • 移动端滑动的默认行为及阻止
  • 表单输入的默认行为及阻止
  • 其它默认行为及阻止

阻止事件的冒泡传播

  • 事件传播机制:捕获、冒泡

  • 京东放大镜案列:mouseover和mouseenter

  • 事件委托

  • 小米商城左侧菜单案列

  • 多级机构菜单 DOM2级事件

  • dom0和dom2区别:事件池机制

  • window.onload和$(document).ready() DOM2兼容问题出路

  • 基础语法兼容处理

  • this问题、重复问题、顺序问题

事件应用

  • 拖拽案列的实现
  • h5中drag事件的应用
  • 弹性势能动画
  • ...

事件基础:全新认识事件

1、什么是事件?

事件是元素天生具备的行为方式(和写不写JS代码没关系),当我们去操作元素的时候会触发元素的很多事件

2、事件绑定

给当前元素的某一个事件绑定方法,目的是为了让当前元素某个事件被触发的时候,可以做一些事情

给某一个事件绑定方法,目前常用的有两种方式:

1、DOM0级事件绑定

oBox.οnclick=function(){}

2、DOM2级事件绑定

oBox.addEventListener('click',function(){},false) 标准浏览器

oBox.attachEvent('onclick',function(){}) IE6~8

jq里的$('xxx').on()方法就是利用dom2级事件绑定来实现的,后面的框架里的事件都是基于dom0和dom2来封装的

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.οninput=function(){}

移动端没有鼠标,所以鼠标类的事件在移动端兼容都特别的差(mousexxx这些事件不要想着在移动端用了)

移动端的大部分操作是靠手指完成的,移动端独有手指事件

  • 单手指事件模型:touchstart、touchmove、touchend、touchcancel…

  • 多手指事件模型:gesturestart、gesturechange、gestureend…

移动端还有很多操作是基于手机硬件完成的,例如:传感器、陀螺仪、重力感应器等

在移动端兼容click事件,PC端的click是点击(不管点几次都当做点击一次处理),但是移动端把click事件当做单击:移动端使用click事件处理点击操作存在300MS延迟,300MS内没有再点击就把click事件当做单击,否则是双击。

2、事件对象基础概念

事件绑定:给oBox的click事件,基于DOM0级事件绑定的方式,绑定了一个方法;以后当我们手动触发oBox的click行为的时候,会把绑定的方法执行;

oBox.onclick = function (e) {
    //=>arguments[0] === e:当方法执行的时候,浏览器默认传递给方法的参数值(事件对象)
    console.log(e);
};
复制代码

当元素的某一个事件行为被触发,不仅会把之前绑定的方法执行,而且还会给当前绑定的方法传递一个值(浏览器默认传递的), 我们把传递的这个值称为 事件对象。 为什么叫做事件对象呢?

1、因为这个值是个对象类型的值,里面存储了很多的属性和方法

2、这个对象中存储的值都是当前操作的一些基本信息,例如:鼠标的位置、触发的行为类型、触发的事件源等

以上所说都是针对于标准浏览器,IE6~8下不是这样的机制

IE6~8方法被触发执行的时候,浏览器并没有把事件对象当做值传递给函数(e在IE6~8下是undefined);但是IE6~8也有事件对象,事件对象需要我们通过window.event单独获取;

oBox.onclick = function (e) {
    //=>以后想要获取事件对象,我们最好写两套处理,先验证是否传递E,没传递到WINDOW上去找即可
    e = e || window.event;
    console.dir(e);
}
复制代码

事件对象是为了记录当前本次操作基本信息的,所以只和本次操作有关:本次操作,页面中不管通过什么方式获取的e或者window.event(也不管在哪获取的),他们存储的基本信息应该是相同的

3、鼠标事件对象及兼容处理

鼠标事件对象 MouseEvent: MouseEvent是鼠标对象类,每一个鼠标事件对象都是当前类MouseEvent的一个实例

clientX / clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴坐标

pageX / pageY:当前鼠标触发点距离BODY左上角的X/Y轴坐标(页面第一屏幕左上角),但是IE6~8中没有这两个属性

type:当前触发事件的类型

target:事件源(当前鼠标操作的是哪一个元素,那么事件源就是谁),IE6~8下没有target这个属性,它有srcElement这个属性代表事件源

preventDefault:此方法是为了阻止事件的默认行为,IE6~8下没有这个方法,需要使用e.returnValue=false来处理

stopPropagation:此方法是为了阻止事件的冒泡传播,IE6~8不兼容,需要使用e.cancelBubble=true处理

1.oBox.onclick=function(e){
2.    if(typeof e==='undefined'){
3.        //=>IE6~8
4.        e = window.event;
5.
6.        //=>pageX / pageY
7.        e.pageX=e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft);
8.        e.pageY=e.clientY+(document.documentElement.scrollTop||document.body.scrollTop);
9.
10.        //=>target
11.        e.target=e.srcElement;
12.
13.        //=>preventDefault
14.        e.preventDefault = function(){
15.            e.returnValue=false;
16.        }
17.
18.        //=>stopPropagation
19.        e.stopPropagation=function(){
20.            e.cancelBubble=true;
21.        }
22.    }
23.    //=>下面在使用属性或者方法的时候,完全按照标准浏览器的语法去实现即可(IE6~8下不兼容的属性和方法我们已经重写为兼容的了)
        // e.pageX
        // e.target
        // e.preventDefault();
24.}
复制代码

ie和火狐浏览器有哪些区别?(意思就是标准和低版本ie678有哪些区别)

在事件对象上,ie低版本事件对象是通过window.event来获取;标准浏览器是通过浏览器传递的值获取的

事件对象获取方式不一样导致事件对象上一些属性也存在兼容性问题:pageX在ie低版本中没有,需要通过e.pageX=e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft)获取

e.target需要通过e.target=e.srcElement;获取

阻止事件默认行为和阻止事件冒泡在ie中也不兼容,需要通过 e.returnValue=false来处理

上面的兼容处理方式属于比较完整的,但是如果项目中我们只想用到一个不兼容的属性,我们没必要写这么多,简单处理一下兼容就可以

1.oBox.onclick=function(e){
2.    e=e||window.event;
3.
4.    e.target=e.target||e.srcElement;
5.
6.    e.preventDefault?e.preventDefault():e.returnValue=false;
7.}
复制代码

4、键盘事件对象及推箱子案列

键盘事件对象 KeyboardEvent: KeyboardEvent是键盘对象类,每一个键盘事件对象都是当前类KeyboardEvent的一个实例

code :当前键盘的按键,例如:按删除键,存储的是’Backspace’ (IE6~8下没有这个属性) ,还有一个叫做key的属性和code一样,存储的也是按键的名称 (IE6~8下没有这个属性)

keyCode:存储的是当前键盘按键对应的码值(大部分按键都有自己的码值)

which:和keyCode一样对应的也是键盘码的值(它不兼容IE6~8)

1.inputBox.onkeyup=function(e){
2.    e=e||window.event;
3.    var code=e.which||e.keyCode;
4.}
复制代码

应用键盘事件对象,完成推箱子的效果

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="css/reset.min.css">
    <style>
        html, body {
            height: 100%;
            overflow: hidden;
            background: lightcyan;
        }

        .box {
            position: absolute;
            top: 100px;
            left: 200px;
            width: 100px;
            height: 100px;
            background: red;
        }
    </style>
</head>
<body>
<div class="box" id="box"></div>

<script src="js/jquery-1.11.3.min.js"></script>
<script>
    var $box = $('#box');
    var minL = 0,
            minT = 0,
            maxL = (document.documentElement.clientWidth || document.body.clientWidth) - $box[0].offsetWidth,
            maxT = (document.documentElement.clientHeight || document.body.clientHeight) - $box[0].offsetHeight;

    $(document).on('keydown', function (e) {
        //=>JQ中的事件绑定已经把E的兼容处理都完成了,我们只需要按照标准浏览器中的写法操作即可
        var code = e.which,
                curL = parseFloat($box.css('left')),//盒子当前的left值
                curT = parseFloat($box.css('top'));//=>JQ中CSS方法获取的结果是默认不去单位的(我们需要自己去除单位)
        switch (code) {
            case 37:
                curL -= 10;
                break;
            case 39:
                curL += 10;
                break;
            case 38:
                curT -= 10;
                break;
            case 40:
                curT += 10;
                break;
        }
        //->在设置样式前,我们需要做一下边界判断
        curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
        curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
        $box.css({
            left: curL,
            top: curT
        });

        //=>按下空格键,让元素蹦一下
        if (code === 32) {
            $box.stop().animate({top: curT - 60}, 500, function () {
                $box.stop().animate({top: curT}, 500);
            });
        }
    });
</script>
</body>
</html>
复制代码

5、移动端事件对象及解决click事件300ms延迟问题

移动端手指事件对象 TouchEvent

touches & changedTouches & targetTouches:存储的是当前屏幕上每一个手指操作的位置信息

touches:只有手指在屏幕上我们才可以获取对应的信息值(手机离开屏幕没有相关信息了,这样touchend事件中我们无法通过touches获取手指信息)

changedTouches:手指在屏幕上的时候,和touches获取的信息一样,但是它可以记录手指离开屏幕一瞬间所在的位置信息(最常用的)

var oBox = document.querySelector('#box');
oBox.ontouchstart = function (e) {
    console.dir(e);
};
oBox.ontouchend = function (e) {
    console.dir(e);
};
复制代码
// 打印出来的结果
1.TouchEvent
2.   type:'touchstart',
3.   target:事件源,
4.   touches:
5.      0:{
6.          clientX:xxx,
7.          clientY:xxx,
8.          pageX:xxx,
9.          pageY:xxx
10.          ...
11.      }
12.      ...
13.      length:1
14.   changeTouches以及targetTouches存储的结构和touches相同
15.   ...  
复制代码

我们知道移动端的click是单击事件(不是PC端的点击效果),存在300ms的延迟,项目中我们需要解决这个延迟: 使用touchstart、touchmove、touchend来处理

简单处理

1.oBox.ontouchend=function(){
2.    //=>不管你是怎么操作的,我只需要知道,手指离开就算点击即可,存在一些问题:
3.    //=>手指按住屏幕不松开,时间超过750ms应该算作长按,不是点击,手指离开不应该按照点击处理
4.    //=>手指在屏幕上滑动了,此时应该算作滑动不是点击,手指离开屏幕也不应该按照点击处理
5.    //...
6.}
复制代码

详细处理一下

// var oBox = document.querySelector('#box');
// oBox.ontouchstart = function (e) {
//     console.dir(e);
// };
// oBox.ontouchend = function (e) {
//     console.dir(e);
// };

let oBox = document.querySelector('#box');
oBox.ontouchstart = function (e) {
    let point = e.changedTouches[0];

    //=>记录当前手指的起始坐标位置(记录在当前元素的自定义属性上:在其它方法中如果我们想要获取的话,直接通过自定义属性获取即可。也可以记录到全局变量,但是全局变量多了性能不好)
    this.strX = point.pageX;
    this.strY = point.pageY;
    this.isMove = false;
};
oBox.ontouchmove = function (e) {
    let point = e.changedTouches[0];
    //=>一般我们手指操作,都会或多或少的发生一些偏移(习惯性偏移),此时不应该算作滑动,只有滑动的距离超出一定范围,我们按照滑动处理即可(一般都是把10px作为偏差值)
    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;
    }
};
oBox.ontouchend = function (e) {
    let point = e.changedTouches[0];
    //=>手指离开的时候:验证是否发生滑动
    if (!this.isMove) {
        //=>点击操作
        console.log('我是点击操作~~');
        return;
    }
    //=>滑动操作
    let dir = null;
    if (Math.abs(this.changeX) > Math.abs(this.changeY)) {
        //=>左右滑动
        dir = this.changeX < 0 ? 'LEFT' : 'RIGHT';
    } else {
        //=>上下滑动
        dir = this.changeY < 0 ? 'UP' : 'DOWN';
    }
    console.log(`当前手指滑动的方向为:${dir}`);
};
复制代码

在移动端开发中,我们需要的一些操作(例如:点击、单击、双击、长按、滑动[四方向]…)都是基于内置原生的 touchstart\touchmove\touchend 事件一点点模拟出来的效果,没有现成的事件

而多手指操作(例如:旋转、缩放…)都是基于gesture事件模型模拟出来的效果

目前市场上有很多成熟的类库或者插件,专门为大家把常用的操作进行了封装,我们直接调取使用即可

1、fastclick.js:目的就是为了解决移动端click事件300ms延迟的问题(如果我们的移动端使用了click事件,我们只需要把这个JS导入配置一下即可)

2、百度云touch手势事件库(已经没有维护了)

3、hammer.js(中国用得少,全世界用的最多的一个)

4、zepto.js:提供移动端事件操作的板块,也是目前市场上使用率最高的(小型JQ)

zepto.js的使用

1.$('.box').tap(function(){
2.    //=>点击
3.});
4.$('.box').singleTap(function(){
5.    //=>单击
6.});
7.$('.box').doubleTap(function(){
8.    //=>双击
9.});
10.$('.box').longTap(function(){
11.    //=>长按
12.});
13.$('.box').swipe(function(){
14.    //=>滑动  .swipeLeft/.swipeRight/.swipeUp/.swipeDown
15.});
16.//=> .pinchIn(function(){}) 缩小
17.//=> .pinchOut(function(){}) 放大
18.//...
复制代码

6、A标签的默认行为及阻止

A标签都有哪些默认行为

1、超链接:点击A标签可以实现页面的跳转

2、锚点定位:通过HASH(哈希)值定位到当前页面指定ID元素的位置

真实项目中我们想用A标签做一个普通的按钮(优势:它的:hover样式是兼容所有浏览器的),此时就要把之前提到的两个默认行为阻止掉才可以

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="css/reset.min.css">
    <style>
        #box {
            margin: 2000px auto;
            width: 100px;
            height: 100px;
            background: red;
        }
    </style>
</head>
<body>
<!--超链接默认行为-->
<!--<a href="http://www.baidu.com/">百度</a>
<a href="http://www.baidu.com/" target="_blank">百度-新窗口</a>-->

<!--
 锚点定位(哈希定位):
   定位到当前页面中元素的ID和HASH值(#xxx哈希值)相同的元素位置
   1、首先在URL地址栏的末尾追加一个HASH值
   2、如果地址中有HASH值,浏览器除了页面渲染外,在渲染完成后,会默认定位到指定HASH值的元素位置
-->
<!--<a href="#box">快速定位到红色盒子</a>
<div id="box"></div>-->


</body>
</html>
复制代码

阻止A标签的默认行为:

1.//=>在HTML中阻止默认行为(最常用)
2.<a href='javascript:;'></a>
3.<a href='javascript:void 0;'></a>
1./*
2. *  <a href='http://www.zhufengpeixun.cn' id='link'></a>
3. */
4.//=>当点击A标签的时候
5.//1、首先会触发click事件
6.//2、其次按照href中的地址进行页面跳转
7.link.onclick=function(){
8.
9.    return false;//=>在函数中返回一个FALSE(只能是FALSE)也可以阻止默认行为
10.}
11.
12.link.onclick=function(e){
13.
14.    //=>通过事件对象阻止默认行为
15.    e=e||window.event;
16.    e.preventDefault?e.preventDefault():e.returnValue=false;
17.}
复制代码

7、事件的传播机制

事件传播有三个阶段

Event.prototype

0 NONE:默认值,不代表任何的意思

1 CAPTURING_PHASE:捕获阶段

2 AT_TARGET:目标阶段(当前事件源)

3 BUBBLING_PHASE:冒泡阶段

三个阶段处理的事情

当前元素的某个事件行为被触发,它所有祖先元素(一直到document)的相关事件行为也会被依次触发(顺序:从内向外),如果祖先祖先元素的这个行为绑定了方法,绑定的方法也会被触发执行,我们把事件的这种传播机制叫做:冒泡传播

8、事件传播机制的一点补充(不重要)

outer.addEventListener('click', function () {
    console.log(`OUTER 1`);
}, false);

outer.addEventListener('click', function () {
    console.log(`OUTER 2`);
}, true);//=>当前绑定的方法会在捕获阶段触发执行

inner.addEventListener('click', function () {
    console.log(`INNER`);
}, false);//=>FALSE:当前绑定的方法是在目标阶段或者冒泡传播阶段才会被触发执行(等价于DOM0事件绑定)
复制代码

传播三个阶段的先后顺序,先捕获再目标再冒泡。true和false是控制这个方法要在哪个阶段执行

9、mouseover事件和mouseenter事件的区别

mouseover和mouseenter的区别

mouseover:鼠标滑到元素上 mouseenter:鼠标进入元素里面

[1] mouseover 存在事件的冒泡传播机制,而mouseenter 浏览器把它的事件冒泡传播机制阻止了

[2] 鼠标从父元素进入到子元素 over:先触发父元素的mouseout(因为鼠标已经不再父元素上了,mouseover本意是鼠标在元素上才算触发),在触发子元素的mouseover(由于冒泡传播机制导致父元素的mouseover也被重新触发了)

enter:进入,从大盒子进入到小盒子,没有触发大盒子的mouseleave事件,但是也触发了小盒子的mouseenter,浏览器阻止了它的冒泡传播,所以大盒子的mouseenter不会被触发

html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="css/reset.min.css">
    <style>
        html, body {
            height: 100%;
            overflow: hidden;
        }

        #outer {
            margin: 20px auto;
            
            padding: 20px;
            width: 200px;
            height: 200px;
            background: red;
        }

        #inner {
            width: 200px;
            height: 200px;
            background: green;
        }
    </style>
</head>
<body>
<div id="outer">
    <div id="inner"></div>
</div>

<script src="js/5-3.js"></script>
</body>
</html>
复制代码

js

outer.onmouseover = function () {
    console.log(`outer over`);
};
outer.onmouseout = function () {
    console.log(`outer out`);
};

inner.onmouseover = function (e) {
    console.log(`inner over`);
};
inner.onmouseout = function (e) {
    console.log(`inner out`);
};

//---------------------------------------------------------------------------


outer.onmouseover = function () {
    console.log(`outer over`);
};
outer.onmouseout = function () {
    console.log(`outer out`);
};

inner.onmouseover = function (e) {
    console.log(`inner over`);
    e = e || window.event;
    e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
};
inner.onmouseout = function (e) {
    console.log(`inner out`);
    e = e || window.event;
    e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
};

//---------------------------------------------------------------------------
// 项目中常用  因为不会触发事件冒泡机制
outer.onmouseenter = function () {
    console.log(`outer enter`);
};
outer.onmouseleave = function () {
    console.log(`outer leave`);
};

inner.onmouseenter = function (e) {
    console.log(`inner enter`);
};
inner.onmouseleave = function (e) {
    console.log(`inner leave`);
};
复制代码

10、事件委托原理及改写轮播图

事件的传播机制引发出来的:事件委托/事件代理

利用事件的冒泡传播机制完成(mouseenter不存在委托机制,因为它不存在冒泡传播)

一个容器中很多元素的同一个事件行为都要绑定方法,此时我们没有必要在获取所有元素,一个个的绑定方法了,我们只需要给最外层元素的这个事件绑定一个方法,这样不管里面哪一个元素的这个事件行为被触发,都会利用冒泡传播机制,把外层容器绑定的那个方法执行,在方法执行的时候,我们可以根据事件源判断出操作的是哪个元素,从而做不同的事情(使用事件委托这样完成的操作比一个个的单独事件绑定性能提高50%左右)

事件委托原理及改写轮播图

11、京东放大镜案列之结构和样式~12、京东放大镜案列之使用JS实现具体的需求

首先鼠标在mark盒子正中间,mark盒子是相对smallBox来定位的,所以我们要获取到mark相对于smallBox盒子的top和left值,由图,我们可以获取到鼠标的pageX,pageY和盒子的offsetLeft,offsetTop,然后鼠标要在mark盒子正中间,所以还要获取到mark盒子宽度的一半和长度的一半,然后就可以计算出mark相对于smallBox盒子的top和left值

magnifier.less

@import "reset";

@w: 300;
@h: 300;
.main {
  margin: 20px auto;
  width: unit(@w*2, px);

  .smallBox {
    position: relative;
    float: left;
    width: unit(@w, px);
    height: unit(@h, px);
    overflow: hidden;

    img {
      display: block;
      width: 100%;
      height: 100%;
    }

    .mark {
      position: absolute;
      top: 0;
      left: 0;
      width: unit(@w/3, px);
      height: unit(@h/3, px);
      background: orangered;
      opacity: 0.3;
      filter: alpha(opacity=50);
      cursor: move;
      border-radius: 50%;
    }
  }

  .bigBox {
    .smallBox;
    display: none;
    border-radius: 50%;

    img {
      display: block;
      //=>左侧MARK/左侧盒子 = 右侧盒子/右侧IMG
      position: absolute;
      top: 0;
      left: 0;
      width: unit(@w*3, px);
      height: unit(@h*3, px);
    }
  }
}
复制代码

1-放大镜.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet/less" href="css/magnifier.less">
    <script src="js/less-2.5.3.min.js"></script>
</head>
<body>
<div class="main clearfix">
    <div class="smallBox" id="smallBox">
        <img src="img/1.jpg" alt="">
        <div class="mark" id="mark"></div>
    </div>

    <div class="bigBox" id="bigBox">
        <img src="img/2.jpg" alt="">
    </div>
</div>

<script src="js/magnifier.js"></script>
</body>
</html>
复制代码

magnifier.js

// 单例模式

// let magnifierRender = (function () {
   

//     return {
//         init: function () {

//         }
//     }
// })();
// magnifierRender.init();



let magnifierRender = (function () {
    let smallBox = document.getElementById('smallBox'),
        bigBox = document.getElementById('bigBox'),
        mark = document.getElementById('mark'),
        bigImg = bigBox.getElementsByTagName('img')[0];

    //=>当前元素如果是隐藏的,是无法通过盒子模型属性获取它宽度的(所以css中我们没有设置mark的display='none')
    //=>或者 先把需要获取的隐藏元素的宽度或者高度设置成null,再在显示之后去获取给变量赋值
    //=>这一步如果没有获取到mark.offsetWidth和mark.offsetHeight,鼠标不会居中,计算会失误

    // let markW = null,
    //     markH = null,
    //     smallW = smallBox.offsetWidth,
    //     smallH = smallBox.offsetHeight;
    // let maxL = null,
    //     maxT = null;

    let markW = mark.offsetWidth,
        markH = mark.offsetHeight,
        smallW = smallBox.offsetWidth,
        smallH = smallBox.offsetHeight;

    let maxL = smallW - markW,
        maxT = smallH - markH;

    //=>计算MARK盒子的位置
    let computedMark = function (e) {
        e = e || window.event;
        //->计算鼠标在MARK中间时候,MARK的TOP&LEFT值
        let curL = e.clientX - smallBox.offsetLeft - markW / 2,
            curT = e.clientY - smallBox.offsetTop - markH / 2;
        //->边界判断
        curL = curL < 0 ? 0 : (curL > maxL ? maxL : curL);
        curT = curT < 0 ? 0 : (curT > maxT ? maxT : curT);
        //->设置MARK的样式
        mark.style.left = curL + 'px';
        mark.style.top = curT + 'px';

        //->MARK跟随鼠标移动,我们也需要让BIG-IMG也跟着移动
        //1、MARK向右移动,BIG-IMG整体向左移动(移动方向是相反的)
        //2、MARK移动多少,BIG-IMG在MARK移动的基础上乘以3
        bigImg.style.left = -curL * 3 + 'px';
        bigImg.style.top = -curT * 3 + 'px';
    };

    //=>给SMALL-BOX的相关事件绑定方法
    let bindEvent = function () {
        smallBox.onmouseenter = function (e) {
            //->进入SMALL-BOX:展示MARK和BIG-BOX,计算MARK当前的位置
            mark.style.display = 'block';
            bigBox.style.display = 'block';
            computedMark(e);
        };

        smallBox.onmousemove = function (e) {
            //->在SMALL-BOX中移动:随时计算MARK的位置,让MARK跟着鼠标走
            computedMark(e);
        };

        smallBox.onmouseleave = function (e) {
            //->离开SMALL-BOX:隐藏MARK和BIG-BOX
            mark.style.display = 'none';
            bigBox.style.display = 'none';
        };
    };

    return {
        init: function () {
            mark.style.display = 'none';//->不在CSS中隐藏:防止开始MARK是隐藏的,我们无法获取MARK的宽度和高度(开始是显示的,当我们获取到宽高后,在把它隐藏即可)
            bindEvent();
        }
    }
})();
magnifierRender.init();

复制代码

其它比例计计算方式

鼠标跟随放大镜

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>珠峰培训</title>
    <link rel="stylesheet" href="css/reset.min.css">
    <style>
        .imgBox {
            position: relative;
            margin: 20px auto;
            width: 500px;
        }

        .imgBox .imgList li {
            float: left;
            margin-right: 18px;
            width: 100px;
            height: 75px;
            border: 1px solid #EEE;
        }

        .imgBox .imgList li img {
            display: block;
            width: 100%;
            height: 100%;
        }

        .imgBox .mark {
            display: none;
            position: absolute;
            width: 400px;
            height: 300px;
            border: 1px solid #EEE;
        }

        .imgBox .mark img {
            display: block;
            width: 100%;
            height: 100%;
        }
    </style>

    <!--IMPORT JS-->
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
        $(document).ready(function () {
            var $imgBox = $('.imgBox'),
                    $itemList = $imgBox.find('.imgList>li'),
                    $mark = null;

            $itemList.on('mouseenter', function (e) {
                //->E:JQ已经把鼠标事件对象处理到兼容所有浏览器了
                //->THIS:当前操作的这个LI [JS对象]
                var bigImg = $(this).children('img').attr('data-big');//->获取到大图的地址

                //->如果还没有创建过MARK,我们则创建一个MARK,如果已经存在,我们只需要把MARK中图片的地址修改过来即可
                if (!$mark) {
                    $imgBox.append('<div class="mark"><img src="' + bigImg + '" alt=""></div>');//->JQ中的APPEND除了增加JS元素对象外,还可以增加一个标签字符串,也相当于把字符串代表的元素增加到容器的末尾
                    $mark = $imgBox.children('.mark');
                    $mark.stop().show('fast');//->JQ中的快捷动画:SHOW/HIDE/FADE-IN/FADE-OUT/SLIDE-DOWN/SLIDE-UP... (参数:数字时间、'SLOW''FAST'...)
                } else {
                    $mark.children('img').attr('src', bigImg);
                }

                //->计算MARK位置
                markPosition.call(this, e);

            }).on('mouseleave', function () {
                //->把MARK在容器中移除即可
                $mark.remove();//->JQ中的REMOVE就是把当前元素在所在容器中移除
                $mark = null;//->设置它的值为NULL,下一次进入到另外LI中,我们在重新创建一个MARK即可
            }).on('mousemove', markPosition);

            //=>控制MARK的位置
            function markPosition(e) {
                //->THIS:当前操作的LI
                var obj = $imgBox.offset(),
                        curL = e.pageX - obj.left,
                        curT = e.pageY - obj.top;
                $mark.css({
                    left: curL + 20,
                    top: curT + 20
                });
            }
        });
    </script>
</head>
<body>
<div class="imgBox">
    <ul class="imgList clear">
        <!--后期需要使用大图,我们大图的地址放在自定义属性上:DATA-BIG-->
        <li><img src="img/apple_1.jpg" data-big="img/apple_1_bigger.jpg" alt=""></li>
        <li><img src="img/apple_2.jpg" data-big="img/apple_2_bigger.jpg" alt=""></li>
        <li><img src="img/apple_3.jpg" data-big="img/apple_3_bigger.jpg" alt=""></li>
        <li><img src="img/apple_4.jpg" data-big="img/apple_4_bigger.jpg" alt=""></li>
    </ul>
    <!--<div class="mark"><img src="img/apple_1_bigger.jpg" alt=""></div>-->
</div>
<!--
思考题:
    我们当前的案例还有另外一种实现的办法
    1、首先每一个LI中都有一个MARK,事先准备好的,只是开始是隐藏的
    2、鼠标进入这个LI,控制MARK显示即可,调整位置(鼠标移动的时候也调整位置)
    3、鼠标离开,隐藏MARK即可
-->
</body>
</html>
复制代码

13、树形菜单案列之基础结构和样式~14、树形菜单案列之使用事件委托实现我们的操作

tree.less

@import "reset";

html, body {
  height: 100%;
  overflow: hidden;
}

.menu {
  padding: 0 10px;
  width: 250px;
  height: 100%;
  background: lightgoldenrodyellow;
  background: -webkit-linear-gradient(top left, lightblue, lightcyan, linen, lightgoldenrodyellow);
  overflow: auto;

  h3 {
    position: relative;
    padding-left: 20px;
    line-height: 30px;
    font-size: 14px;
    font-weight: normal;

    em {
      position: absolute;
      top: 50%;
      left: 0;
      margin-top: -8px;
      width: 16px;
      height: 16px;
      background: url("../img/icon.png") no-repeat;

      &.plus {
        background-position: -59px -28px;
      }

      &.minus {
        background-position: -42px -29px;
      }
    }
  }

  .level2, .level3, .level4 {
    display: none;
    margin-left: 25px;
  }
}
复制代码

2-tree.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet/less" href="css/tree.less">
    <script src="js/less-2.5.3.min.js"></script>
</head>
<body>
<div class="menu">
    <ul class="level1">
        <li>
            <!--plus:加号 minus:减号-->
            <h3><em class="plus"></em>产品技术部门</h3>
            <ul class="level2">
                <li>
                    <h3><em class="plus"></em>产品部门</h3>
                    <ul class="level3">
                        <li><h3>需求分析小组</h3></li>
                        <li><h3>UE体验小组</h3></li>
                        <li><h3>UI设计师小组</h3></li>
                    </ul>
                </li>
                <li>
                    <h3><em class="plus"></em>开发部分</h3>
                    <ul class="level3">
                        <li>
                            <h3><em class="plus"></em>前端开发</h3>
                            <ul class="level4">
                                <li><h3>传统前端开发</h3></li>
                                <li><h3>APP开发</h3></li>
                                <li><h3>NODE后台开发</h3></li>
                                <li><h3>虚拟现实开发</h3></li>
                            </ul>
                        </li>
                        <li><h3>后台开发</h3></li>
                        <li><h3>架构师团队</h3></li>
                        <li><h3>DB数据分析团队</h3></li>
                        <li><h3>公共技术研发团队</h3></li>
                    </ul>
                </li>
                <li><h3>测试部门</h3></li>
                <li><h3>运维部门</h3></li>
            </ul>
        </li>

        <li>
            <h3><em class="plus"></em>运营推广部门</h3>
            <ul class="level2">
                <li>
                    <h3><em class="plus"></em>产品部门</h3>
                    <ul class="level3">
                        <li><h3>需求分析小组</h3></li>
                        <li><h3>UE体验小组</h3></li>
                        <li><h3>UI设计师小组</h3></li>
                    </ul>
                </li>
                <li>
                    <h3><em class="plus"></em>开发部分</h3>
                    <ul class="level3">
                        <li>
                            <h3><em class="plus"></em>前端开发</h3>
                            <ul class="level4">
                                <li><h3>传统前端开发</h3></li>
                                <li><h3>APP开发</h3></li>
                                <li><h3>NODE后台开发</h3></li>
                                <li><h3>虚拟现实开发</h3></li>
                            </ul>
                        </li>
                        <li><h3>后台开发</h3></li>
                        <li><h3>架构师团队</h3></li>
                        <li><h3>DB数据分析团队</h3></li>
                        <li><h3>公共技术研发团队</h3></li>
                    </ul>
                </li>
                <li><h3>测试部门</h3></li>
                <li><h3>运维部门</h3></li>
            </ul>
        </li>
    </ul>
</div>

<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/tree.js"></script>
</body>
</html>
复制代码

tree.js

let treeRender = (function () {
    let $menu = $('.menu');

    //=>事件委托实现TREE展开或者收起
    let bindEvent = function () {

        $menu.on('click', function (e) {
            // jq已经把事件对象处理兼容了,以后直接按照标准浏览器的语法使用即可
            let target = e.target,
                tarTag = target.tagName,
                $target = $(target);

            if (tarTag === 'EM') {
                target = target.parentNode;
                tarTag = target.tagName;
                $target = $(target);
            }

            //->如果当前的TARGET是H3,我们进行相关的处理
            if (tarTag === 'H3') {
                let $next = $target.next();
                if ($next.length === 0) return;
                let $em = $target.children('em');
                $em.toggleClass('minus');
                $next.stop().slideToggle('fast', function () {
                    if (!$em.hasClass('minus')) {
                        //=>已经把当前结构收起了:后代都要收起
                        $next.find('ul').css('display', 'none');
                        $next.find('em').removeClass('minus');
                    }
                });
            }
        });
    };

    return {
        init: function () {
            bindEvent();
        }
    }
})();
treeRender.init();

复制代码

或者tree-backup.js

let treeRender = (function () {
    let $menu = $('.menu');

    //=>事件委托实现TREE展开或者收起
    let bindEvent = function () {

        // $menu.click(function(e){});
        // $menu.bind('click',function(){});
        // 以上两种写法也行,但是jq内部也会转成on绑定,所以这里直接用on绑定来实现(on绑定是jq中最核心最低层的事件绑定方法,所有用的方法最终都用到on)
        // $menu.delegate('h3','click',function(e){});
        // $menu.delegate('em','click',function(e){});
        // JQ中提供的事件委托方法(老版本使用的是live方法,jq内部也是用on绑定来实现的)

        // 事件委托除了性能比循环高,还可以给后续动态追加的元素实现事件绑定,(用循环还需要从新获取后期新增元素,性能消耗)
        // live可以操作动态绑定元素,on不行。这种说法是错误的,live之所以能操作动态绑定的元素主要原因是因为事件委托实现的,
        // 在on里面我们也可以自己实现事件委托,所以在on里面也可以操作动态绑定的元素

        $menu.on('click', function (e) {
            //->JQ已经把事件对象处理兼容了
            //以后直接按照标准浏览器的语法使用即可
            let target = e.target,
                tarTag = target.tagName,
                $target = $(target);//把获取到的原生对象的标签名转换成jq对象
            //->点击EM或者H3做相同的事情,我们首先把事件源统一:如果点击的是EM,我们让TARGET也等于H3,此时我们后续再操作的时候,都以H3为标准操作即可
            if (tarTag === 'EM') {
                target = target.parentNode;
                tarTag = target.tagName;
                $target = $(target);
            }

            //->如果当前的TARGET是H3,我们进行相关的处理
            if (tarTag === 'H3') {
                let $next = $target.next();//这里得到的是jq集合。需要通过length判断有没有对象,jq集合没有对象值是一个空对象也不是null,不能作为判断条件
                if ($next.length === 0) return;//->当前H3所在层级是没有下一级UL的,我们什么都不需要处理;下面代码肯定是有下一级结构;

                let $em = $target.children('em');
                if ($em.hasClass('plus')) {
                    //->说明:当前是折叠的结构,我们需要展开结构
                    //1、显示下一级结构
                    $next.stop().slideDown('fast');
                    //2、让EM改变为减号
                    $em.addClass('minus').removeClass('plus');
                } else {
                    //->说明:当前是展开的结构,我们需要折叠结构
                    $next.stop().slideUp('fast', function () {
                        //->当动画完成后执行的操作
                        //->当把当前层级收起的时候:我们还需要把它后代中的相关层级也一并收起,这样再次展开当前层级,后代层级默认是收起的
                        //->this:当前收起的这个UL($(this)<=>$next)
                        $(this).find('ul')
                            .css('display', 'none');
                        $(this).find('em').removeClass('minus')
                            .addClass('plus');
                    });
                    $em.addClass('plus').removeClass('minus');
                }
            }
        });
    };

    // $menu.click(function(e){});
    // $menu.bind('click',function(){});
    //=>JQ中提供的事件委托方法(老版本使用的是LIVE方法)
    // $menu.delegate('h3','click',function(e){});
    // $menu.delegate('em','click',function(e){});

    return {
        init: function () {
            bindEvent();
        }
    }
})();
treeRender.init();

复制代码

15~16、拖拽案列之基础结构样式和拖拽实现的原理及实现基本的功能

js中的DOM0和DOM2事件

事件应用

  • 拖拽案列的实现
  • H5中drag事件的应用
  • 弹性势能动画
  • ...

6-drag.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <!--<link rel="stylesheet/less" href="css/login.less">
    <script src="js/less-2.5.3.min.js"></script>-->

    <link rel="stylesheet" href="css/login.min.css">
</head>
<body>
<!--LOGIN-->
<div class="loginBoxMark"></div>
<div class="loginBox" id="loginBox">
    <h3>欢迎登陆</h3>
    <div class="content">

    </div>
</div>

<script src="js/dragES5.js"></script>
</body>
</html>
复制代码

login.less

@import "reset";

html, body {
  height: 100%;
  -webkit-user-select: none;//文字不选中
}

/*--LOGIN--*/
.loginBoxMark {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 998;
  width: 100%;
  height: 100%;
  background: #000;
  opacity: 0.2;
  filter: alpha(opacity=20);
}

.loginBox {
  position: fixed;
  /*top: 50%;
  left: 50%;*/
  z-index: 999;
  /*margin: -250px 0 0 -195px;*/
  width: 390px;
  height: 500px;
  background: #FFF;

  h3 {
    height: 50px;
    line-height: 50px;
    padding-left: 20px;
    font-size: 16px;
    font-weight: normal;
    background: #EEE;
    cursor: move;
    border-bottom: 1px solid #CCC;
  }
}

复制代码

drag-back.js

let dragRender = (function () {
    let loginBox = document.getElementById('loginBox'),
        loginTitle = loginBox.getElementsByTagName('h3')[0];

    let winW = document.documentElement.clientWidth || document.body.clientWidth,//一屏幕的宽度和高度
        winH = document.documentElement.clientHeight || document.body.clientHeight;
    let maxL = winW - loginBox.offsetWidth,//计算可移动范围的最大宽度和长度
        maxT = winH - loginBox.offsetHeight;

    //=>鼠标按下
    let dragDown = function (e) {
        e = e || window.event;

        //->记录鼠标起始位置 & 盒子的起始位置(放到this:loginTitle的自定义属性上)
        this.strX = e.clientX;
        this.strY = e.clientY;
        this.strL = loginBox.offsetLeft;
        this.strT = loginBox.offsetTop;

        //->按下代表拖拽开始(给loginTitle绑定onmousemove和onmouseup,只要鼠标在loginTitle上拖拽,盒子就移动)
        this.onmousemove = dragMove;
        this.onmouseup = dragUp;
    };

    //=>鼠标移动(每个方法中的this都是loginTitle)
    let dragMove = function (e) {
        e = e || window.event;
        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);
        loginBox.style.left = curL + 'px';
        loginBox.style.top = curT + 'px';
    };

    //=>鼠标抬起
    let dragUp = function (e) {
        //->拖拽结束:把MOVE和UP都移除掉
        this.onmousemove = null;
        this.onmouseup = null;
    };

    return {
        init: function () {
            //=>让盒子处于页面的中间(用js计算盒子让盒子居中,因为js只改变了盒子的top和left值,margin没有计算。)
            loginBox.style.left = maxL / 2 + 'px';
            loginBox.style.top = maxT / 2 + 'px';

            loginTitle.onmousedown = dragDown;//=>this:loginTitle
        }
    }
})();
dragRender.init();

复制代码

安装.babelrc将drag-back.js(es6)转成dragES5.js(es5)

1、建立.babelrc文件

2、进入目录下安装模块

3、配置.babelrc

4、安装成功,编译drag.js ,-o:编译一个文件 , -d:编译整个目录 , -w:监听。以后只要这个drag.js改了会自动编译

drag.js转成dragES5.js

'use strict';

var dragRender = function () {
    var loginBox = document.getElementById('loginBox'),
        loginTitle = loginBox.getElementsByTagName('h3')[0];

    var winW = document.documentElement.clientWidth || document.body.clientWidth,
        winH = document.documentElement.clientHeight || document.body.clientHeight;
    var maxL = winW - loginBox.offsetWidth,
        maxT = winH - loginBox.offsetHeight;

    //=>鼠标按下
    var dragDown = function dragDown(e) {
        var _this = this;

        e = e || window.event;
        this.strX = e.clientX;
        this.strY = e.clientY;
        this.strL = loginBox.offsetLeft;
        this.strT = loginBox.offsetTop;

        if (this.setCapture) {
            this.onmousemove = dragMove;
            this.onmouseup = dragUp;
            this.setCapture();
            return;
        }
        document.onmousemove = function (e) {
            dragMove.call(_this, e);
        };
        document.onmouseup = function (e) {
            dragUp.call(_this, e);
        };
    };

    //=>鼠标移动
    var dragMove = function dragMove(e) {
        e = e || window.event;
        var 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;
        loginBox.style.left = curL + 'px';
        loginBox.style.top = curT + 'px';
    };

    //=>鼠标抬起
    var dragUp = function dragUp(e) {
        if (this.releaseCapture) {
            this.onmousemove = null;
            this.onmouseup = null;
            this.releaseCapture();
            return;
        }
        document.onmousemove = null;
        document.onmouseup = null;
    };

    return {
        init: function init() {
            loginBox.style.left = maxL / 2 + 'px';
            loginBox.style.top = maxT / 2 + 'px';

            loginTitle.onmousedown = dragDown;
        }
    };
}();
dragRender.init();

/*
 * 在拖拽案例中有一个经典的问题:鼠标焦点丢失问题
 *   当鼠标移动速度过快的时候,鼠标离开了H3(盒子跟不上奔跑的速度),所以导致以下的一些问题:
 *   1、鼠标在H3外边飞,不会触发H3的mousemove,盒子也就不跟着动了
 *   2、鼠标在H3外边抬起,也不会触发H3的mouseup,那么原有绑定的dragMove方法无法被移除,鼠标从新进入H3,此时不管有没有按下鼠标,是要鼠标移动,盒子就跟着跑
 *   ...
 *   =>所有问题都是因为:鼠标离开H3,鼠标的所有操作和H3的事件(以及绑定的方法)没关系了
 *
 * 解决方案一:(所有浏览器都兼容的)
 *   把mousemove和mouseup事件绑定给document,原因:鼠标不管怎么飞都飞不出document,只要你鼠标还在文档中,那么mousemove和mouseup永远生效
 *
 * 解决方案二:不兼容谷歌浏览器
 *   使用setCapture & releaseCapture
 */
复制代码

17、拖拽案列之鼠标焦点丢失问题解决(绑定给document)

drag-backup2.js

let dragRender = (function () {
    let loginBox = document.getElementById('loginBox'),
        loginTitle = loginBox.getElementsByTagName('h3')[0];

    let winW = document.documentElement.clientWidth || document.body.clientWidth,
        winH = document.documentElement.clientHeight || document.body.clientHeight;
    let maxL = winW - loginBox.offsetWidth,
        maxT = winH - loginBox.offsetHeight;

    //=>鼠标按下
    let dragDown = function (e) {
        //=>这里的this还是:loginTitle(H3)
        e = e || window.event;
        this.strX = e.clientX;
        this.strY = e.clientY;
        this.strL = loginBox.offsetLeft;
        this.strT = loginBox.offsetTop;

        //->绑定给DOCUMENT,问题:此时DRAG-MOVE和DRAG-UP方法中的TIS是DOC了,而我们在方法中需要使用H3中记录的信息
        // document.onmousemove = dragMove;
        // document.onmouseup = dragUp;
        // 这样写,是把call()立即执行的结果返回给document.onmousemove,会还没有触发document.onmousemove就执行dragMove.call(this)
        // 这里call()可以改成bind(),但是bind()不兼容
        // document.onmousemove =  dragMove.call(this);
        // document.onmouseup =  dragUp.call(this);

        //->解决:
        let _this = this;
        document.onmousemove = function (e) {
            //=>this:document
            dragMove.call(_this,e);
        };
        document.onmouseup = (e)=> {
            //=>this:宿主环境中的THIS
            dragUp.call(this,e);
        };
    };

    //=>鼠标移动
    let dragMove = function (e) {
        e = e || window.event;
        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);
        loginBox.style.left = curL + 'px';
        loginBox.style.top = curT + 'px';
    };

    //=>鼠标抬起
    let dragUp = function (e) {
        //->移除DOC上绑定的方法
        document.onmousemove = null;
        document.onmouseup = null;
    };

    return {
        init: function () {
            loginBox.style.left = maxL / 2 + 'px';
            loginBox.style.top = maxT / 2 + 'px';

            loginTitle.onmousedown = dragDown;//这个还是在loginTitle(H3)上绑定,因为按到loginTitle(H3)才开始拖拽
        }
    }
})();
dragRender.init();

/*
 * 在拖拽案例中有一个经典的问题:鼠标焦点丢失问题
 *   当鼠标移动速度过快的时候,鼠标离开了H3(盒子跟不上奔跑的速度),所以导致以下的一些问题:
 *   1、鼠标在H3外边飞,不会触发H3的mousemove,盒子也就不跟着动了
 *   2、鼠标在H3外边抬起,也不会触发H3的mouseup,那么原有绑定的dragMove方法无法被移除,鼠标从新进入H3,此时不管有没有按下鼠标,是要鼠标移动,盒子就跟着跑
 *   ...
 *   =>鼠标离开H3,鼠标的所有操作和H3的事件(以及绑定的方法)没关系了
 *
 * 解决方案一:(所有浏览器都兼容的)
 *   把mousemove和mouseup事件绑定给document,原因:鼠标不管怎么飞都飞不出document,只要你鼠标还在文档中,那么mousemove和mouseup永远生效
 *
 * 解决方案二:不兼容谷歌浏览器
 *   使用
 */

复制代码

18、拖拽案列之鼠标焦点丢失问题解决(setCapture)

drag.js

let dragRender = (function () {
    let loginBox = document.getElementById('loginBox'),
        loginTitle = loginBox.getElementsByTagName('h3')[0];

    let winW = document.documentElement.clientWidth || document.body.clientWidth,
        winH = document.documentElement.clientHeight || document.body.clientHeight;
    let maxL = winW - loginBox.offsetWidth,
        maxT = winH - loginBox.offsetHeight;

    //=>鼠标按下
    let dragDown = function (e) {
        e = e || window.event;
        this.strX = e.clientX;
        this.strY = e.clientY;
        this.strL = loginBox.offsetLeft;
        this.strT = loginBox.offsetTop;

        if (this.setCapture) {
            this.onmousemove = dragMove;
            this.onmouseup = dragUp;
            this.setCapture();
            return;
            
        }
        
        document.onmousemove = (e)=> {
            dragMove.call(this, e);
        };
        document.onmouseup = (e)=> {
            dragUp.call(this, e);
        };

    };

    //=>鼠标移动
    let dragMove = function (e) {
        e = e || window.event;
        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);
        loginBox.style.left = curL + 'px';
        loginBox.style.top = curT + 'px';
    };

    //=>鼠标抬起
    let dragUp = function (e) {
        if (this.releaseCapture) {
            this.onmousemove = null;
            this.onmouseup = null;
            this.releaseCapture();
            return;
        }
        document.onmousemove = null;
        document.onmouseup = null;
    };

    return {
        init: function () {
            loginBox.style.left = maxL / 2 + 'px';
            loginBox.style.top = maxT / 2 + 'px';

            loginTitle.onmousedown = dragDown;
        }
    }
})();
dragRender.init();

/*
 * 在拖拽案例中有一个经典的问题:鼠标焦点丢失问题
 *   当鼠标移动速度过快的时候,鼠标离开了H3(盒子跟不上奔跑的速度),所以导致以下的一些问题:
 *   1、鼠标在H3外边飞,不会触发H3的mousemove,盒子也就不跟着动了
 *   2、鼠标在H3外边抬起,也不会触发H3的mouseup,那么原有绑定的dragMove方法无法被移除,鼠标从新进入H3,此时不管有没有按下鼠标,是要鼠标移动,盒子就跟着跑
 *   ...
 *   =>所有问题都是因为:鼠标离开H3,鼠标的所有操作和H3的事件(以及绑定的方法)没关系了
 *
 * 解决方案一:(所有浏览器都兼容的)
 *   把mousemove和mouseup事件绑定给document,原因:鼠标不管怎么飞都飞不出document,只要你鼠标还在文档中,那么mousemove和mouseup永远生效
 *
 * 解决方案二:不兼容谷歌浏览器
 *   使用setCapture & releaseCapture
 */

复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值