鼠標(biāo)大家每天都在用,右鍵單擊鼠標(biāo)后會(huì)出現(xiàn)一個(gè)菜單,下面的文字就嘗試實(shí)現(xiàn)這個(gè)功能,效果如下圖:
為了實(shí)現(xiàn)這個(gè)功能,分解一下我們需要做的事情:
- document對(duì)象監(jiān)聽鼠標(biāo)點(diǎn)擊事件
- 觸發(fā)鼠標(biāo)點(diǎn)擊事件猜憎,獲取鼠標(biāo)當(dāng)前所在瀏覽器的位置
- 根據(jù)當(dāng)前位置調(diào)整contextmenu的顯示坐標(biāo)
- 顯示contextmenu
接下來(lái)一步步實(shí)現(xiàn)每個(gè)過(guò)程。
監(jiān)聽何種事件類型
click ? keydown搔课,keypress胰柑,keyup ? mousedown,mouseup ?
- 首先:不能使用click事件辣辫,DOM event3規(guī)范已經(jīng)明確指出:
Note: click事件應(yīng)該僅能夠被鼠標(biāo)左鍵觸發(fā)旦事,鼠標(biāo)中鍵和右鍵不應(yīng)該觸發(fā)click事件. DOM-Level-3-Events
具體到瀏覽器實(shí)現(xiàn)上:ie各個(gè)版本和chrome是一致的,均不支持鼠標(biāo)右鍵觸發(fā)click事件急灭,firefox實(shí)現(xiàn)了一個(gè)折中的方案姐浮,對(duì)于document元素支持鼠標(biāo)右鍵觸發(fā)click事件,其他元素則不觸發(fā)click事件葬馋。
原因:鼠標(biāo)右鍵以及中鍵支持click事件容易造成click事件的混淆卖鲤,試想一下,我們編寫的絕大多數(shù)click事件處理函數(shù)中畴嘶,并沒(méi)有對(duì)鼠標(biāo)按鍵進(jìn)行區(qū)分蛋逾,但我們默認(rèn)的是鼠標(biāo)左鍵觸發(fā),因此使用鼠標(biāo)右鍵是未預(yù)料的行為窗悯,詳細(xì)信息可以參考這里
keydown区匣,keypress,keyup這三個(gè)事件也不能使用蒋院,這三個(gè)是鍵盤事件亏钩,不能捕獲到鼠標(biāo)事件莲绰,
最終我們選擇的事件就是mousedown和mouseup,在mousedown事件中清除頁(yè)面中已經(jīng)出現(xiàn)的contextmenu姑丑,在mouseup事件中顯示contextmenu
另一個(gè)需要考慮的問(wèn)題是蛤签,如何阻止鼠標(biāo)右鍵單擊的默認(rèn)行為,你可能嘗試在mouseup事件中阻止事件默認(rèn)行為栅哀,但這是沒(méi)用的震肮,因?yàn)橛|發(fā)contextmenu菜單的事件并不是mouseup,而是contextmenu事件留拾,所以要阻止瀏覽器鼠標(biāo)右鍵單擊事件可以這樣寫
document.oncontextmenu = function(){
return false;
}
這里也不必?fù)?dān)心兼容性問(wèn)題戳晌,各個(gè)瀏覽器對(duì)contextmenu事件支持的很好,關(guān)于stopPropagation, preventDefault 和 return false 的區(qū)別痴柔,可以參考這篇文章
獲取鼠標(biāo)位置
chrome瀏覽器的event對(duì)象提供了下面幾組鼠標(biāo)位置坐標(biāo)
- clientX/clientY
- layerX/layerY
- offsetX/offsetY
- pageX/pageY
- screenX/screenY
- webkitMovementX/webkitMovementY
- x/y
IE8不支持pageX/Y躬厌,ff提供了mozMovementX/Y,但是ff沒(méi)有x/y這一組坐標(biāo)竞帽。
真夠多的,選擇哪一組呢鸿捧?建議先閱讀下面的文章屹篓,中文翻譯,原文
- clientX/Y 提供了相對(duì)于viewport的以CSS像素度量的坐標(biāo)
- pageX/Y提供了相對(duì)于<html>元素的以CSS像素度量的坐標(biāo)
- screenX/Y提供了相對(duì)于電腦屏幕的以設(shè)備像素進(jìn)行度量的坐標(biāo)
- offsetX/offsetY提供了相對(duì)于父容器的以CSS像素度量的坐標(biāo)
- layerX/layerY提供了相對(duì)于absolute匙奴,relative元素的坐標(biāo)堆巧,如不存在,則相對(duì)于<html>元素泼菌。
選哪一組值現(xiàn)在就一目了然了谍肤。
(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
發(fā)現(xiàn)一個(gè)BUG:我們的contextmenu并沒(méi)有檢查瀏覽器的邊界,系統(tǒng)自帶的功能是下面這樣的:
邊界檢查
為了做邊界檢查哗伯,需要知道哪些參數(shù)荒揣?
- 鼠標(biāo)相對(duì)于瀏覽器視口的坐標(biāo)
- contextmenu的寬度和高度
- 瀏覽器視口的寬度和高度
獲取鼠標(biāo)的位置跟之前是一樣的
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
}
}