JavaScript事件对象坐标兼容性分析与封装实现
本文详细对比了IE、Firefox、Chrome、Safari及Opera等主流浏览器在事件对象坐标属性(如clientX, pageX, layerX)上的行为差异。针对W3C标准未完全统一及IE怪异模式导致的兼容性问题,提供了两个版本的getEventCoord封装函数,并演示了基于该函数的通用拖拽组件实现。
浏览器版本与坐标属性概览
测试浏览器版本列表如下:
IETester 6 ,7
IE 8.0
Firefox 3.5.5
Chrome 4.1.249.1064 (45376)
Opera 9.64
Safari 4.0
接下来分析各主流浏览器支持的坐标属性及其具体含义。
IE浏览器坐标属性分析
在IE浏览器中:
event.offsetX
event.offsetY
该属性表示相对于事件源元素(e.srcElement)的坐标。
用于设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标。
用于设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标。
event.clientX
event.clientY
这两个属性始终相对于视口(viewport)。
用于设置或获取鼠标指针位置相对于窗口客户区域的 x 坐标,其中客户区域不包括窗口自身的控件和滚动条。
用于设置或获取鼠标指针位置相对于窗口客户区域的 y 坐标,其中客户区域不包括窗口自身的控件和滚动条。
event.x
event.y
尽管文档说明其相对于文档,但在IE6/7中,这两个值与clientX和clientY保持一致。
这并非严重问题,因为通过视口相对坐标加上滚动条已卷去的高度,仍可计算出真实坐标。该问题在IE8的标准模式下得到解决。
用于设置或获取鼠标指针位置相对于父文档的 x 像素坐标。
用于设置或获取鼠标指针位置相对于父文档的 y 像素坐标。
event.screenX
event.screenY
用于设置或获取鼠标指针位置相对于用户屏幕的 x 坐标。
用于设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。
Firefox浏览器坐标属性分析
在FireFox浏览器中:
event.layerX
event.layerY
该属性表示相对于事件源元素(e.srcElement)的坐标。
用于设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标。
用于设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标。
event.clientX
event.clientY
这两个属性始终相对于视口。
用于设置或获取鼠标指针位置相对于窗口客户区域的 x 坐标,其中客户区域不包括窗口自身的控件和滚动条。
用于设置或获取鼠标指针位置相对于窗口客户区域的 y 坐标,其中客户区域不包括窗口自身的控件和滚动条。
event.pageX
event.pageY
用于设置或获取鼠标指针位置相对于父文档的 x 像素坐标。
用于设置或获取鼠标指针位置相对于父文档的 y 像素坐标。
event.screenX
event.screenY
用于设置或获取鼠标指针位置相对于用户屏幕的 x 坐标。
用于设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。
实际上,IE和Firefox已经涵盖了大部分属性,其他浏览器则对这些属性进行了组合,但意义完全一致。
Chrome、Safari与Opera的兼容策略
Chrome 和 Safari
Chrome和Safari的支持非常全面,它们收录了所有坐标属性,包括:
event.offsetX
event.offsetY
event.layerX
event.layerY
event.clientX
event.clientY
event.x
event.y
event.pageX
event.pageY
注意:Chrome和Safari中,event.x和event.y的行为与IE6/7一致,它们等于event.clientX和event.clientY。
Opera遵循了IE6/7的路径,它拥有:
event.offsetX
event.offsetY
event.clientX
event.clientY
event.x
event.y
虽然与IE非常相似,但幸好它提供了pageX和pageY。
event.pageX
event.pageY
注意:Chrome、Safari以及Opera中的event.x和event.y都与IE6/7表现一致,它们等于event.clientX和event.clientY。
而在IE8中,event.x和event.y则等同于其他浏览器的event.pageX和event.pageY。
W3C标准与浏览器实现差异
为什么layerX、offsetX、x和pageX会在某些浏览器中重复出现?
因为W3C并未标准化这些属性,DOM3草案中MouseEvent部分沿用了DOM2的定义,仅包含两对属性。
clientX of type long, readonly
The horizontal coordinate at which the event occurred relative to the viewport associated with the event.
clientY of type long, readonly
The vertical coordinate at which the event occurred relative to the viewport associated with the event
screenX of type long, readonly
The horizontal coordinate at which the event occurred relative to the origin of the screen coordinate system.
screenY of type long, readonly
The vertical coordinate at which the event occurred relative to the origin of the screen coordinate system.
这导致了兼容性问题,因此支持标准的浏览器都失去了方向。但浏览器厂商考虑到W3C标准可能不会进一步细化,于是决定从offsetXY和layerXY、pageXY和xy中各选一对放入浏览器中。
为了迎合标准,这两对属性都被保留在浏览器中。
无论出现何种问题,最终都需要解决。根据上述兼容报告,代码雏形得以形成。
getEventCoord函数封装与优化
我们开始动手编写代码!
getEventCoord
代码如下:
1 var getEventCoord = function( e )
2 {
3 var evt = e||event;
4 var html = document.documentElement; //滚动条在<HTML>上
5 return {
6
7 //如果pageX属性为真 就使用pageX,否则就使用 clientX + html.scrollLeft
8 pageX : evt.pageX || evt.clientX + html.scrollLeft,
9
10 //如果pageY属性为真 就使用pageY,否则就使用 clientY + html.scrollTop
11 pageY : evt.pageY || evt.clientY + html.scrollTop,
12
13 //clientX Y 大家都一致,木有悬念
14 clientX : evt.clientX,
15 clientY : evt.clientY,
16
17 //如果layerX属性为真 就使用layerX,否则就使用 offsetX
18 layerX : evt.layerX || evt.offsetX,
19
20 //如果layerY属性为真 就使用layerY,否则就使用 offsetY
21 layerY : evt.layerY || evt.offsetY
22 }
23 } 用法如下:
代码如下:
document.onmousemove = function( e )
{
var coord = getEventCoord(e);
document.title = [coord.pageX,coord.pageY];
} 看起来已经非常OK,似乎能满足日常工作需要,但仍存在几个问题。
1.不严谨
使用 evt.pageX || evt.clientX + html.scrollLeft 这种判断,
只要evt.pageX 等于 undefined,null,NaN,'',0,false 这些值,左边的表达式结果就为false,从而计算右边的表达式并返回表达式的值,
而evt.pageX本身就是有机会返回0的。所以这条判断应该改为
typeof evt.pageX == 'number' ? evt.pageX : evt.clientX + html.scrollLeft
只有当pageX是个数字的时候才使用它。
2.无法工作在怪异模式中
什么是怪异模式?
IE为了兼容IE56之前版本,在ie6中引入了两种渲染模式: 怪异模式(Quicks Mode) 和 标准模式 (Standards Mode)
两种模式差异主要集中在css的盒模型解释方面,而BOM中。则是滚动条的依赖对象发生了变化
在怪异模式中,滚动条是body的,如果想取得页面的滚动卷去的高度和宽度,需要使用document.body.scrollTop
而标准模式中需要使用document.documentElement.scrollTop
而两种模式的切换方式主要是由doctype来确定,参见:http://dancewithnet.com/2009/06/14/activating-browser-modes-with-doctype/
从ie6开始,ie使用一个属性 document.compatMode 来检测文档是否切换到了怪异模式还是处在标准模式
如果document.compatMode的值
为BackCompat:就是在怪癖模式下
为CSS1Compat:就是在标准模式下
所以这里为了在两个模式中都工作正常,
我们需要判断document.compatMode是哪个模式
而判断的方式也很简单,只需要判断compatMode的值的第一个字母是否为b,就可以选择scrollTop的依赖对象
判断方式可以这么写
document.compatMode.indexOf('b')==0
也可以用正则写
/^b/i.test( document.compatMode )
第二种显得更牛x一点。。嗯,就用第二种(实际上第一种性能好一点)
现在再来写第二版吧
代码
代码如下:
var getEventCoord = function( e )
{
var evt = e||event, d = document,
scrollEl = /^b/i.test( d.compatMode ) ? d.body : d.documentElement,
supportPage = typeof evt.pageX == 'number',
supportLayer = typeof evt.layerX == 'number'
return {
pageX : supportPage ? evt.pageX : evt.clientX + scrollEl.scrollLeft,
pageY : supportPage ? evt.pageY : evt.clientY + scrollEl.scrollTop,
clientX : evt.clientX,
clientY : evt.clientY,
layerX : supportLayer ? evt.layerX : evt.offsetX,
layerY : supportLayer ? evt.layerY || evt.offsetY
}
} 基于坐标封装的拖拽示例
噔噔噔噔,这就搞定了,这个函数能干啥哩,首先想到的就是拖拽,让我们写一个小小的拖拽函数验证一下下
代码
代码如下:
function dragMe( o )
{
var supportCapt = !!o.setCapture;
o.onmousedown = function(e)
{
var coord = getEventCoord(e), x = coord.layerX, y = coord.layerY;
if( supportCapt ) o.setCapture();
document.onmousemove = function(e)
{
var coord = getEventCoord(e);
o.style.left = coord.pageX - x + 'px';
o.style.top = coord.pageY - y + 'px';
}
document.onmouseup = function()
{
this.onmousemove = this.onmouseup = null;
if( supportCapt ) o.releaseCapture();
}
}
}
dragMe( document.getElementById('block') ); 例子
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title> Event </title> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <style> body { font-size:12px } #block { -moz-user-select:none; width:100px; height:100px; background:gray; color:white; line-height:100px; text-align:center; } .move { cursor:move; } </style> </head> <body> <div onselectstart="return false" style="left:100px;top:100px;position:absolute;" id="block"> 拖拽我 </div> <div id="info" ></div> </body> </html> anime.js 快速入门:怎么引入库并编写第一个 DOM 动画
2026-11-05 14:22:43
在线JS解密后变量名仍乱码?结合浏览器DevTools调试还原
2026-11-04 20:26:01
怎么在浏览器中运行JavaScript代码并查看输出结果
2026-11-04 09:07:33
JS 获取屏幕分辨率:window.screen.width 与 height 用法
2026-11-04 08:52:48
为什么推荐用DOMContentLoaded代替window.onload初始化JS
2026-11-04 08:42:33
