鼠标大家每天都在用,右键单击鼠标后会出现一个菜单,下面的文字就尝试实现这个功能,效果如下图:
为了实现这个功能,分解一下我们需要做的事情:
- document对象监听鼠标点击事件
- 触发鼠标点击事件,获取鼠标当前所在浏览器的位置
- 根据当前位置调整contextmenu的显示坐标
- 显示contextmenu
接下来一步步实现每个过程。
监听何种事件类型
click ? keydown,keypress,keyup ? mousedown,mouseup ?
- 首先:不能使用click事件,DOM event3规范已经明确指出:
Note: click事件应该仅能够被鼠标左键触发,鼠标中键和右键不应该触发click事件. DOM-Level-3-Events
具体到浏览器实现上:ie各个版本和chrome是一致的,均不支持鼠标右键触发click事件,firefox实现了一个折中的方案,对于document元素支持鼠标右键触发click事件,其他元素则不触发click事件。
原因:鼠标右键以及中键支持click事件容易造成click事件的混淆,试想一下,我们编写的绝大多数click事件处理函数中,并没有对鼠标按键进行区分,但我们默认的是鼠标左键触发,因此使用鼠标右键是未预料的行为,详细信息可以参考这里
keydown,keypress,keyup这三个事件也不能使用,这三个是键盘事件,不能捕获到鼠标事件,
最终我们选择的事件就是mousedown和mouseup,在mousedown事件中清除页面中已经出现的contextmenu,在mouseup事件中显示contextmenu
另一个需要考虑的问题是,如何阻止鼠标右键单击的默认行为,你可能尝试在mouseup事件中阻止事件默认行为,但这是没用的,因为触发contextmenu菜单的事件并不是mouseup,而是contextmenu事件,所以要阻止浏览器鼠标右键单击事件可以这样写
document.oncontextmenu = function(){
return false;
}
这里也不必担心兼容性问题,各个浏览器对contextmenu事件支持的很好,关于stopPropagation, preventDefault 和 return false 的区别,可以参考这篇文章
获取鼠标位置
chrome浏览器的event对象提供了下面几组鼠标位置坐标
- clientX/clientY
- layerX/layerY
- offsetX/offsetY
- pageX/pageY
- screenX/screenY
- webkitMovementX/webkitMovementY
- x/y
IE8不支持pageX/Y,ff提供了mozMovementX/Y,但是ff没有x/y这一组坐标。
真够多的,选择哪一组呢?建议先阅读下面的文章,中文翻译,原文
- clientX/Y 提供了相对于viewport的以CSS像素度量的坐标
- pageX/Y提供了相对于<html>元素的以CSS像素度量的坐标
- screenX/Y提供了相对于电脑屏幕的以设备像素进行度量的坐标
- offsetX/offsetY提供了相对于父容器的以CSS像素度量的坐标
- layerX/layerY提供了相对于absolute,relative元素的坐标,如不存在,则相对于<html>元素。
选哪一组值现在就一目了然了。
(function(){
var menu = document.createElement("div");
menu.id="contextmenu";
menu.className="hidden";
document.body.appendChild(menu);
bindEvent(document , "contextmenu" , closeContextMenu);
bindEvent(document , "mouseup" , openNewContextMenu);
bindEvent(document , "mousedown" , closeNewContextMenu);
function closeContextMenu(){
return false;
}
function openNewContextMenu( ev ){
ev = ev || window.event;
var btn = ev.button;
if( btn == 2){
menu.style.left = ev.clientX +"px";
menu.style.top = ev.clientY +"px";
menu.className = "show";
}
}
function closeNewContextMenu( ev ){
menu.className = "hidden";
}
function bindEvent(elem , eventType , callback){
var ieType = ["on" + eventType ];
if( ieType in elem ){
elem[ ieType ] = callback;
}else if("attachEvent" in elem){
elem.attachEvent(ieType ,callback);
}else{
elem.addEventListener(eventType ,callback , false);
}
}
})();
//css
#contextmenu{
width:180px;
height: 240px;
background-color:#f2f2f2;
position:absolute;
border:1px solid #BFBFBF;
box-shadow:2px 2px 3px #aaaaaa;
}
.show{
display:block;
}
.hidden{
display:none;
}
效果图:
BUG
发现一个BUG:我们的contextmenu并没有检查浏览器的边界,系统自带的功能是下面这样的:
边界检查
为了做边界检查,需要知道哪些参数?
- 鼠标相对于浏览器视口的坐标
- contextmenu的宽度和高度
- 浏览器视口的宽度和高度
获取鼠标的位置跟之前是一样的
x = ev.clientX ,
y = ev.clientY ;
获取浏览器视口的尺寸
document.documentElement.clientWidth;
document.documentElement.clientHeight;
获取contextmenu的宽度和高度
menu.offsetWidth
menu.offsetHeight
最终的代码:
function getNextContextMenuPostion( ev ){
var x = ev.clientX ,y = ev.clientY ,
html = document.documentElement, vx = html.clientWidth , vy = html.clientHeight,
mw = menu.offsetWidth, mh = menu.offsetHeight;
return {
left : (x + mw) > vx ? (vx - mw ) : x,
top : (y + mh) > vy ? (vy - mh ) : y
}
}