原生js的event事件兼容,火狐隐式获取event的方法
2017-01-16 20:19

        在我们写js的时候经常要用到event对象,你可能用它来阻止事件冒泡,或者阻止浏览器的默认行为,也可能得到鼠标的点击位置,鼠标的按键信息,还有可能得到键盘的按键信息,功能键的状态等。

        确实,event对象是一个合格的理想的对象,但是这个对象还有点小脾气,这就需要你细心照顾,对她无微不至,她才能对你死心塌地。

        event的对象用处确实不少,你要实现右键菜单,得有她的帮助,你要实现鼠标画图,得有她的支持,你要实现输入过滤,得有她的帮助,你要实现拖动还得有她的帮助才行。诸如等等,在我们前端的页面中,event对象就像一个贤内助,有她的帮助,你才可以实现各种你想要的效果。

        首先,我们先看看怎么正确的追求event这个对象呢?

<div class='cont' onclick='getEvent()'>
function getEvent(){
    var evt=window.event||arguments.callee.caller.arguments[0];
    }

    如果我们的事件中不给传递event对象的情况下,在IE中,event对象是作为window的属性的全局变量,我们可以很轻松的得到。但是在火狐浏览器下,event对象是一种现场对象,只有在事件触发的时候才会生成,她会作为火狐浏览器的响应事件的第一个参数传入。为什么是浏览器响应事件,而不是事件的响应函数呢?各人理解是这样的:

    浏览器在发生click事件时,是调用了浏览器自身的click回调函数,这个函数的第一个参数是event对象,而后由这个函数调用我们定义的onclick响应函数。这个对于火狐浏览器,我们就需要得到浏览器的回调函数,然后再索要第一个参数,这样也就得到了event对象。

    对于上面的代码:arguments代表函数的参数列表,是js函数中的隐式对象,而arguments.callee代表被调用的函数,也就是函数自身,arguments.callee.caller代表调用它的函数,在这里就取到了浏览器的click回调函数,然后在得到第一个参数arguments[0],也就是event对象了,结合起来就是:arguments.callee.caller.arguments[0];这个在火狐下得到event对象的方法有一个限制,那就是函数不能在层次调用后再通过此方法得到event对象。

<div class='cont' onclick='getEvent()'>
function getEvent(){
       initEvent();
    }
function initEvent(){
     var evt=window.event||arguments.callee.caller.arguments[0];//火狐下将得不到event对象。
}

像上面这种写法就是层次调用了,这个时候在内层方法中火狐用此方法是得不到event对象的。


为了兼容和方便event获取,我们在响应事件的函数中常采用以第一参数传递event对象的放大得到event对象。

<div class='cont' onclick='getEvent2(event)'></div>
function getEvent2(evt){
    var et=evt;
}

这样通过传输event对象到方法中,就可以直接得到event对象使用了。这就像找对象,有人给你牵线搭桥,你们就可以直接步入正题,要没有人给牵线搭桥,你只能像上面那样要先搭讪认识,然后才能追求。搭讪搭不好,还有可能被当成流氓呢。

    而对于event对象,我们的一大用途就是得到鼠标当前的位置:

    为此,浏览器提供了event对象的位置属性-event.clientX,event.offsetX,event.x,event.screenX,event.pageX,event.layerX.

    而这些属性在各个浏览器中存在了些许的兼容性问题。

    下面我们做一个event事件位置的测试

    

.cont{height:500px;background:cyan;margin-bottom:10px;position:relative;}
.out{padding:20px;background:yellow;}
<div class='out'>
<div class='cont' onclick='getEvent2(event)'></div>
</div>
function getEvent2(evt){
  var div=document.createElement("div");
  var x=evt.pageX||evt.clientX+(document.documentElement.scrollLeft||window.pageXOffset||document.body.scrollLeft);
  var y=evt.pageY||evt.clientY+(document.documentElement.scrollTop||window.pageYOffset||document.body.scrollTop);
    //这里要加括号,不然在IE6下会计算错误
  x=x-document.documentElement.clientLeft;
    y=y-document.documentElement.clientTop;//去除IE7-的clientTop,clientLeft的2px差距。其他浏览器为0
  div.style.cssText="position:absolute;left:"+x+"px;top:"+y+"px;width:10px;height:10px;border:1px solid #000;";
  document.body.appendChild(div);
  console.log("event.clientX:"+evt.clientX+"-event.offsetX:"+evt.offsetX+"-event.x:"+evt.x+":-event.screenX:"+evt.screenX+"-event.pageX:"+evt.pageX+"-event.layerX:"+evt.layerX);
}

把上面的代码在各个浏览器中测试,点击div或出现一个黑色的小方框。

 1、cont为relative定位,document没有滚动条,cont没有滚动条

    IE7:

    IE8:

 IE9:

 IE10:

chrome:

火狐:

2、cont为relative,cont没有滚动条,document有滚动值。

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:

3、out为relative,cont没有滚动条,document没有滚动值

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:

3、out为relative,cont没有滚动条,document有滚动值

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:

4、没有relative,document没有滚动值

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:

5、没有relative,document有滚动值,第一个值cont没有滚动值,第二个cont有滚动值

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:


6、没有relative,cont有滚动条,document没有滚动条

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:

7、cont为relative,cont有滚动条,document无滚动值

IE7:

IE8:

IE9:

IE10:

chrome:

火狐:


现在打印结果都有了,我们来分析一下event这几个属性的意义和兼容方法。


一、在chrome浏览器下,offsetY,layerY永远是相对于目标元素的距离,并且是从元素盒子的左上角计算的,并且不受滚动影响。

二、在火狐浏览器下,offsetY,也永远是相对于目标元素的距离,但是是从元素盒子边框内开始计算的,可能为负值,layerY也是相对于元素目标的距离,从盒子边框内开始计算,但是会加上元素的滚动距离。

三、在IE10+下,offsetY是相对于目标元素计算的距离,也是从盒子边框内开始计算,可能为负值,忽略元素滚动条。layerY是相对于relative元素的距离开始计算的。从元素盒子左上角计算,若没有relative ,layerY和clientY相等。

四、在IE9下,offsetY相对于目标元素计算的距离,从盒子边框内开始计算,可能为负值,忽略滚动条,layerY实现是相对于relative定位元素进行计算+滚动条的值,若没有relaitve,则layerY和pageY的值相等。

五、在IE8下,没有layerY和pageY,offsetY从盒子边框内开始计算,可能为负值,忽略元素滚动条,

六、在IE7-下,没有layerY和pageY,offsetY从盒子边框内开始计算,可能为负值,要加上元素滚动条的值。   

通过如上分析,我们可以看出,要兼容offsetY,layerY是比较麻烦的:

只有在cont元素没有滚动条,时候我们可以做如下兼容:

根据火狐下面没有event.y属性,而且event.y在chrome下标示clientY的特性

 var element=event.srcElement||event.target;
  var  bortop=parseInt(window.getComputedStyle ? window.getComputedStyle(element,null).borderTopWidth: element.currentStyle.borderTopWidth); 
  var  oy=(!!(evt.y!=undefined&&evt.layerY!=undefined&&evt.offsetY!=undefined&&(evt.offsetY==evt.layerY)))?(evt.offsetY||evt.layerY):(bortop+(evt.offsetY||evt.layerY));
这里和undefined比较是因为如果点击的位置为0,在chrome下会成为false。

虽然上面测试的火狐也有offsetY属性,但是是基于高版本火狐测试的,低版本火狐是没有此属性点的。

如果cont元素有滚动条,在不考虑IE7-和火狐低版本浏览器的情况下,上述兼容代码也是可以使用的。


另一个属性event.screenY这个各个浏览器表现都一致,都是到屏幕最上方的距离,可以放心使用,在IE7,8下表现的与IE9有差别是因为IE7,8的客户区大小略小。

而event.pageY是表示到文档顶部的距离,event.clientY表示到客户区顶部的距离


上面是一个浏览器文档的草图,表示clientY和pageY的意义。可以看出他们相差的就是一个滚动距离的值。

绘图功底是差了点,意思表示清楚了就行了。

    要兼容IE低版本的pageY代码就需要加上scrollTop滚动变量的值。

var x=evt.pageX||evt.clientX+(document.documentElement.scrollLeft||window.pageXOffset||document.body.scrollLeft);
var y=evt.pageY||evt.clientY+(document.documentElement.scrollTop||window.pageYOffset||document.body.scrollTop);

这个兼容代码在上面已经写过了,其中的pageYOffset是safari浏览器的兼容,safari中没有scrollTop的值。如果要获取鼠标相对于客户区的位置,改变一下顺序即可

var cx=evt.clientX||evt.pageX-(document.documentElement.scrollLeft||window.pageXOffset||document.body.scrollLeft);
var cy=evt.clientY||evt.pageY-(document.documentElement.scrollTop||window.pageYOffset||document.body.scrollTop);

到这里,event对象的位置属性就差一个event.x,event.y属性了,这个属性是IE的属性,不过chrome浏览器现在也支持。

从上面测试中观察event.y属性,可以总结出:

    event.y在IE中类似火狐的layerY属性,若在relative元素中,则是相对于此元素的位置,并且不相对于边框的内角计算,可谓layerY的IE替代,若是在非relative元素中,和clientY值相同(IE7-相差一个document.documentElement.clientTop的值,2px的差距)。在chrome浏览器中event.y不论是否relative定位都和clientY的值相同。所以,event.x,event.y的兼容性灵活性很大。

 1,在relative定位中,可以更简单的和layerY兼容,

var ix=evt.layerX||evt.x;
var iy=evt.layerY||evt.y;

顺序不能改变,不然在chrome浏览器下就变成了clientX了。

 2,在非relative定位中,可以替代clientX

var cx=evt.x||evt.pageX-(document.documentElement.scrollLeft||window.pageXOffset||document.body.scrollLeft);
var cy=evt.y||evt.pageY-(document.documentElement.scrollTop||window.pageYOffset||document.body.scrollTop);

当然,这种兼容性的代码尽量不要用,因为他们和布局定位密切相关,如果定位改变,那么代码就会出现问题。


总结:而这些位置属性常用的,就是得到clientX,pageX,这两个位置的值,若要计算offsetX,offsetY的值还有一个简单些的兼容写法。

var ele=evt.srcElement||evt.target;
var cx=evt.clientX||evt.pageX-(document.documentElement.scrollLeft||window.pageXOffset||document.body.scrollLeft);
var cy=evt.clientY||evt.pageY-(document.documentElement.scrollTop||window.pageYOffset||document.body.scrollTop);
var rect=ele.getBoundingClientRect();
var ox=cx-rect.left-document.documentElement.clientLeft;
var oy=cy-rect.top-document.documentElement.clientTop;

这种兼容写法比之上面的offsetX的兼容更加通用,而且不用考虑是否relative,是否元素有滚动条距离,在各个浏览器下兼容良好。

这里值得一提的就是这个getBoundingClientRect();这个方法返回元素在客户区的位置大小,在IE8-浏览器包括left,top,right,bottom四个值,在IE9+,火狐,chrome浏览器还包括width,height两个值。

其中left,top标示元素盒子左上角到浏览器左上角的距离,right,bottom标示元素盒子左上角到浏览器左上角的距离,IE7浏览器相差一个document.documentElement.clientLeft的2px距离。而通过这四个值可以计算出元素的width,height的大小。所以要兼容使用,可以不用高版本浏览器的width,height属性。

 这篇文章我们介绍了,浏览器中event对象的获取,和event对象的位置属性,及其兼容写法,希望对大家能有所帮助,上面的测试话费了很长时间。

 接下来的文章我们将继续介绍原生js操作dom元素的更多内容,和event对象的键盘事件按键信息等相关内容。敬请期待。

原创文章,转载请注明来自:妹纸前端-www.webfront-js.com.
阅读(2598)
辛苦了,打赏喝个咖啡
微信
支付宝
妹纸前端
妹纸前端工作室 | 文章不断更新中
京ICP备16005385号-1