js实现自定义contextmenu

鼠标大家每天都在用,右键单击鼠标后会出现一个菜单,下面的文字就尝试实现这个功能,效果如下图:

js实现自定义contextmenu_第1张图片
效果图

为了实现这个功能,分解一下我们需要做的事情:

  • 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提供了相对于元素的以CSS像素度量的坐标
  • screenX/Y提供了相对于电脑屏幕的以设备像素进行度量的坐标
  • offsetX/offsetY提供了相对于父容器的以CSS像素度量的坐标
  • layerX/layerY提供了相对于absolute,relative元素的坐标,如不存在,则相对于元素。

选哪一组值现在就一目了然了。

    (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并没有检查浏览器的边界,系统自带的功能是下面这样的:

js实现自定义contextmenu_第2张图片
系统自带

边界检查

为了做边界检查,需要知道哪些参数?

  • 鼠标相对于浏览器视口的坐标
  • 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
    }
}

你可能感兴趣的:(js实现自定义contextmenu)