<前情提要>
<练习1:当鼠标在areaDiv中移动时,在showMsg中来显示鼠标的坐标>
HTML 代码
复制代码<div id="areaDiv"></div> <div id="showMsg"></div>
CSS 代码
复制代码#areaDiv { border: 1px solid black; width: 300px; height: 50px; margin-bottom: 10px; } #showMsg { border: 1px solid black; width: 300px; height: 20px; }
JS 代码
复制代码var areaDiv = document.getElementById("areaDiv"); var showMsg = document.getElementById("showMsg"); // 绑定鼠标移动事件 areaDiv.onmousemove = function(event){ console.log(event); // IE8:undefined // clientX可以获取鼠标指针的水平坐标 // cilentY可以获取鼠标指针的垂直坐标 var x = event.clientX; var y = event.clientY; showMsg.innerHTML = "x = " + x + ", y = " + y; }
效果
内置浏览器
Chrome
Edge
IE11
IE8
在IE8中,响应函数被触发时,浏览器不会传递事件对象
在IE8及以下的浏览器中,是将事件对象作为window
对象的属性保存的
那么按照之前学习到的思路,我们可以对其进行兼容性改造
复制代码var x; var y; if (event) { x = event.clientX; y = event.clientY; } else{ x = window.event.clientX; y = window.event.clientY; } showMsg.innerHTML = "x = " + x + ", y = " + y;
IE8测试
感觉上述代码不优雅,对上述代码进行二次改造
复制代码//if (!event) { // event = window.event; //} event = event || window.event; var x = event.clientX; var y = event.clientY;
<练习2:div跟随鼠标移动>
复制代码// 兼容性写法 event = event || window.event; var left = event.clientX; var top = event.clientY; // div随鼠标移动,注意style属性是有单位的 box1.style.left = (left - box1.clientWidth / 2) + "px"; box1.style.top = (top - box1.clientHeight / 2) + "px";
但是,当我们给body设置一个较大height
属性值时,会发现一个问题,就是鼠标指针与 div 之间存在一定距离
这是为什么呢?
clientX
和clientY
用于获取鼠标在当前的可见窗口的坐标 div 的偏移量,是相对于整个页面的pageX
和pageY
可以获取鼠标相对于当前页面的坐标,但是这个两个属性在IE8中不支持,所以如果需要兼容IE8,则不要使用var left = event.pageX; var top = event.pageY;
再试下效果
貌似好了哈,那直接测试下 IE8?
这要怎么办?
我们现在给 body 设置了一个height
,红色框表示可见区域大小,蓝色框表示 body 的实际区域大小
既然我们没办法使用pageX
和pageY
兼容IE8,那暂时只能使用clientX
和clientY
了,而clientX
和clientY
是按照可见区域大小计算的,那让 div 的水平和垂直偏移量也按照可见区域大小计算不就行了吗?但是我们又暂时没办法让 div 总是参考可见区域大小的原点作为定位的原点,难道就没有办法了吗?
我们之前学习过,scrollTop
表示滚动条的垂直滚动距离,而div位置原点 与鼠标指针原点的差距应该刚好是滚动条垂直滚动的距离,那么是不是可以利用这两个属性来“弥补” 这两者之间的距离差呢?
box1.style.top = (document.body.scrollTop + top - box1.clientHeight / 2) + "px";
发现还是不行,要知道我们是给 body 设置的height
属性,之所以出现滚动条是因为 body 的父元素容不下 body 了,所以应该获取谁的scrollTop
属性?body 的父元素,即 html
box1.style.top = (document.documentElement.scrollTop + top - box1.clientHeight / 2) + "px";
Chrome
IE8
在视频中,测试的结果是Chrome和火狐等浏览器获取scrollTop
的对象不一致,需要做兼容
复制代码chrome认为浏览器的滚动条是body的,可以通过body.scrollTop来获取火狐等浏览器认为浏览器的滚动条是html的,
var st = document.body.scrollTop || document.documentElement.scrollTop;
但是不知道什么原因(浏览器对scrollTop
和scrollLeft
都统一兼容了?毕竟视频是几年前的了),我这里并没有这个问题,所以上述问题存疑,待考究,后面以我实际代码测试结果为准
同理,当水平方向有滚动条时,也要消除水平方向上的距离差,所以综合代码如下
复制代码box1.style.left = (document.documentElement.scrollLeft + left - box1.clientWidth / 2) + "px"; box1.style.top = (document.documentElement.scrollTop + top - box1.clientHeight / 2) + "px";
我这里通过documentElement
获取的scrollLeft
和scrollTop
在 Chrome、Edge、IE11、IE8 中均正常
HTML 代码
复制代码<div id="box1"> 我是div <span id="s1"> 我是span </span> </div>
CSS 代码
复制代码#box1{ width:200px; height: 200px; background-color: #99FF99; } #s1{ background-color: yellowgreen; }
JS 代码
复制代码document.getElementById("s1").onclick = function(){ alert("我是span"); // 我是span 我是div 我是body 我是HTML }; document.getElementById("box1").onclick = function(){ alert("我是div"); // 我是div 我是body 我是HTML }; document.body.onclick = function(){ alert("我是body"); // 我是body 我是HTML }; document.documentElement.onclick = function(){ alert("我是HTML"); // 我是HTML };
所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发
在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡
可以将事件对象的cancelBubble
设置为true
,即可取消冒泡
document.getElementById("s1").onclick = function(event){ // 兼容event event = event || window.event; alert("我是span"); // 我是span event.cancelBubble = true; };
HTML 代码
复制代码<button type="button" id="btn">Add</button> <ul id="ulDiv"> <li><a href="javascript:;">超链接1</a></li> <li><a href="javascript:;">超链接2</a></li> <li><a href="javascript:;">超链接3</a></li> </ul>
JS 代码
复制代码function clickFun(){ alert("超链接"); } window.onload = function(){ // 为每一个超链接都绑定一个单击响应函数 var aList = document.getElementsByTagName("a"); for(var i=0;i<aList.length;i++){ aList[i].onclick = clickFun; } var btn = document.getElementById("btn"); var ulDiv = document.getElementById("ulDiv"); btn.onclick = function(){ var li = document.createElement("li"); li.innerHTML = "<a href=\"javascript:;\">add超链接</a>"; ulDiv.appendChild(li); }; };
这里我们为每一个超链接都绑定了一个单击响应函数,这种操作比较麻烦
而且这些操作只能为已有的超链接设置事件,而新添加的超链接必须重新绑定
我们希望,只绑定一次事件,即可应用到多个的元素上,即使元素是后添加的
我们可以尝试将其绑定给元素的共同的祖先元素
复制代码ulDiv.onclick = function(){ alert("事件委派超链接"); };
事件委派是指将事件统一绑定给元素的共同的祖先元素
这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件
事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能
但是也有个问题,我们是给整个 ul 绑定的单击响应事件,ul 是块元素,在超链接所在行点击任何位置都会触发事件
那怎么办呢?我们就需要再加一层判断: 如果触发事件的对象是我们期望的元素,则执行否则不执行
那怎么知道触发事件的对象是什么呢?
复制代码ulDiv.onclick = function(event){ event = event || window.event; // 如果触发事件的对象是我们期望的元素,则执行否则不执行 // alert(event.target); // 点击超链接外:[object HTMLLIElement]; 点击超链接:javascript:; if(event.target.className == "link"){ alert("事件委派超链接"); } };
但是这种写法有点问题,当其class
属性有多个时,就不对了
<li><a href="javascript:;" class="link hello">超链接1</a></li> <!-- 失效 --> <li><a href="javascript:;" class="link">超链接2</a></li> <li><a href="javascript:;" class="link">超链接3</a></li>
我这里将tagName
代替className
作为判断条件进行判断
ulDiv.onclick = function(event){ event = event || window.event; if(event.target.tagName == "A" || event.target.tagName == "a"){ alert("事件委派超链接"); } };
使用对象.事件 = 函数
的形式绑定响应函数,它只能同时为一个元素的一个事件绑定一个响应函数
不能绑定多个,如果绑定了多个,则后边会覆盖掉前边的
复制代码var btn = document.getElementById("btn"); // 为btn绑定一个单击响应函数 btn.onclick = function() { alert(1); }; // 为btn绑定第二个响应函数 btn.onclick = function() { alert(2); // 2 };
addEventListener()
通过这个方法也可以为元素绑定响应函数,参数:
on
false
使用addEventListener()
可以同时为一个元素的相同事件同时绑定多个响应函数
这样当事件被触发时,响应函数将会按昭函数的绑定顺序执行
复制代码btn.addEventListener("click", function(){ alert(1); // 1 }, false); btn.addEventListener("click", function(){ alert(2); // 2 }, false); btn.addEventListener("click", function(){ alert(3); // 3 }, false);
我们直接在 IE8 中进行测试,这个方法不支持IE8及以下的浏览器
那说了半天,IE8 需要用什么方法替代呢?
attachEvent()
在 IE8 中可以用来绑定事件,参数:
on
btn.attachEvent("onclick", function(){ alert(1); // 1 }); btn.attachEvent("onclick", function(){ alert(2); // 2 }); btn.attachEvent("onclick", function(){ alert(3); // 3 });
继续测试,在 IE8 中没有报错,但是执行顺序却是相反的,而且其他浏览器中直接就不行了
总结: 这个方法也可以同时为一个事件绑定多个处理函数,不同的是它是后绑定先执行,执行顺序和addEventListener()
相反
看起来,我们还是要自己封装一个方法来兼容不同的浏览器
复制代码// 定义一个函数,用来为指定元素绑定响应函数 // 参数: // - obj 要绑定事件的对象 // - eventStr 事件的字符串 // - callback 回调函数 function bind(obj, eventStr, callback) { if (obj.addEventListener) { obj.addEventListener(eventStr, callback, false); } else { obj.attachEvent("on" + eventStr, callback); } }
我们调用下只能自定义的bind
函数
bind(btn, "click", function() { alert(1); });
测试下效果,发现在 IE8 和其他浏览器中均支持
好,我们接着再看个问题
复制代码bind(btn, "click", function() { alert(this); // IE8: [object window];非IE8:[object HTMLButtonElement] });
测试发现,在 Chrome 中打印的是[object HTMLButtonElement]
而在 IE8 中打印的却是[object window]
addEventListener()
中的this
是绑定事件的对象,attachEvent()
中的this
是window
,需要统一两个方法this
我们之前讲过call
和apply
方法,this
是指定的那个对象,是不是可以利用call
或者apply
方法对bind
函数进行优化呢?
function bind(obj, eventStr, callback) { if (obj.addEventListener) { obj.addEventListener(eventStr, callback, false); } else { // this是谁由调用方式决定 // callback.call(obj) obj.attachEvent("on" + eventStr, function(){ // 在匿名函数中调用回调函数 callback.call(obj); }); } }
关于事件的传播网景公司和微软公司有不同的理解
如果希望在捕获阶段就触发事件,可以将addEventListener()
的第三个参数设置为true
一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false
IE8 及以下的浏览器中没有捕获阶段
onmousedown
onmousemove
onmouseup
HTML 代码
复制代码<div id="box1"></div> <div id="box2"></div>
CSS 代码
复制代码#box1 { width: 100px; height: 100px; background-color: red; position: absolute; } #box2 { width: 100px; height: 100px; background-color: yellow; position: absolute; left: 300px; top: 300px; }
JS 代码
复制代码var box1 = document.getElementById("box1"); // 1. 当鼠标在被拖拽元素上按下时,开始拖拽 `onmousedown` box1.onmousedown = function(event) { event = event || window.event; var boxLeft = event.clientX - box1.offsetLeft; // 鼠标水平坐标 - 元素水平偏移量 = 鼠标原点和元素原点水平距离 var boxTop = event.clientY - box1.offsetTop; // 鼠标垂直坐标 - 元素垂直偏移量 = 鼠标原点和元素原点垂直距离 // 2. 当鼠标移动时被拖拽元素跟随鼠标移动 `onmousemove` document.onmousemove = function(event) { event = event || window.event; box1.style.left = event.clientX - boxLeft + "px"; box1.style.top = event.clientY - boxTop + "px"; }; // 3. 当鼠标松开时,被拖拽元素固定在当前位置 `onmouseup` document.onmouseup = function(event) { // 取消document的onmousemove事件 document.onmousemove = null; // 取消document的onmouseup事件 document.onmouseup = null; }; };
效果
当我们拖拽一个网页中的内容时,浏览器会默认去搜索引擎中搜索内容,此时会导致拖拽功能的异常,这个是浏览器提供的默认行为
如果不希望发生这个行为,则可以通过return false
来取消默认行为
但是这招对 IE8 不起作用
那有什么方法可以兼容 IE8 呢?我们先接着往下看
var btn1 = document.getElementById("btn1"); var btn2 = document.getElementById("btn2"); btn1.onclick = function() { alert(1); } btn2.onclick = function() { alert(2); } // 设置btn1对鼠标按下相关的事件进行捕获 // 当调用一个元素的setCapture()方法以后,这个元素将会把下一次所有的鼠标按下相关的事件捕获到自身上 btn1.setCapture();
我们点击 btn2 按钮,发现只有刷新后的第一次点击的提示为1,再次点击就变成了2
我们可以利用setCapture()
方法对 IE8 浏览器的默认行为进行限制吗?当拖拽元素时捕获事件,取消拖拽时释放对事件的捕获
var box1 = document.getElementById("box1"); // 1. 当鼠标在被拖拽元素上按下时,开始拖拽 `onmousedown` box1.onmousedown = function(event) { // 设置box1捕获所有鼠标按下的事件 // 只有IE支持,但是在火狐中调用时不会报错,而如果使用Chrome调用,会报错 box1.setCapture && box1.setCapture(); event = event || window.event; var boxLeft = event.clientX - box1.offsetLeft; // 鼠标水平坐标 - 元素水平偏移量 = 鼠标原点和元素原点水平距离 var boxTop = event.clientY - box1.offsetTop; // 鼠标垂直坐标 - 元素垂直偏移量 = 鼠标原点和元素原点垂直距离 // 2. 当鼠标移动时被拖拽元素跟随鼠标移动 `onmousemove` document.onmousemove = function(event) { event = event || window.event; box1.style.left = event.clientX - boxLeft + "px"; box1.style.top = event.clientY - boxTop + "px"; }; // 3. 当鼠标松开时,被拖拽元素固定在当前位置 `onmouseup` document.onmouseup = function(event) { // 取消document的onmousemove事件 document.onmousemove = null; // 取消document的onmouseup事件 document.onmouseup = null; // 当鼠标松开时,取消对事件的捕获 box1.releaseCapture && box1.releaseCapture(); }; // 取消默认行为 return false; };
测试在 IE8 中的效果
如果我想拖动 div2呢?这个时候我们需要封装一个函数,方便我们直接传参调用
复制代码// 拖拽方法封装成一个函数 function draw(obj){ obj.onmousedown = function(event) { obj.setCapture && obj.setCapture(); event = event || window.event; var boxLeft = event.clientX - obj.offsetLeft; var boxTop = event.clientY - obj.offsetTop; document.onmousemove = function(event) { event = event || window.event; obj.style.left = event.clientX - boxLeft + "px"; obj.style.top = event.clientY - boxTop + "px"; }; document.onmouseup = function(event) { document.onmousemove = null; document.onmouseup = null; obj.releaseCapture && obj.releaseCapture(); }; return false; }; }
HTML 代码
复制代码<div id="box1"></div> <div id="box2"></div> <img src="img/an.jpg" id="img" style="width: 320px;height: 320px;position: absolute;left:200px;top:400px;"/>
JS 代码调用函数
复制代码var box1 = document.getElementById("box1"); var box2 = document.getElementById("box2"); var img = document.getElementById("img"); draw(box1); draw(box2); draw(img);
效果
Chrome
IE8