2017 百度前端技术学院 - 自定义右键菜单

2,963 阅读5分钟
原文链接: www.jianshu.com

转载自:http://www.jianshu.com/p/731488a8a3e6

效果预览

一、事件流

1、冒泡

  • 什么是事件冒泡
    官方的定义就是从最特定的事件目标到最不特定的事件目标。
    意思就是说,假如用户单击了一个元素,该元素拥有一个click事件,那么同样的事件也将会被它的祖先触发,这个事件从该元素开始一直冒泡到DOM树的最上层,这一过程称为事件冒泡

示例:

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义右键菜单</title>
</head>
<body>
    <ul id='menu'>
        <li>menu item 1</li>
        <li>menu item 2</li>
    </ul>
</body>
</html>

js

document.oncontextmenu =function (){
    alert('触发了);
}

效果

2、捕获

  • 什么是事件捕获
    事件捕获和事件是相反的,也就是说,当用户触发了一个事件的时候,这个事件是从DOM树的最上层开始触发一直到捕获到事件源.

标准的监听事件方式(标准浏览器都可使用,IE9以上)

    element.addEventListener(eventType, fn, false)
说明:
  • 第一个参数: eventType:事件类型
  • 第二个参数:fn 触发的回调函数
  • 第三个参数:一个布尔值,表示冒泡还是捕获。false表示冒泡,true表示捕获

IE下的监听事件方式(IE专有)

·attachEvent(eventType,fn)· IE专有。

说明:

此种监听方式只有冒泡没有捕获。

注意:
  • 标准监听方式不需加on

       document.addEventlistener('contextmenu',function (){
       alert('我是标准的我不需要on')
     },false)
  • IE监听事件方式必须加on

    document.attachEvent('contextmenu',function(){
             alert('我是IE的我需要on')
    })

一般使用标准即可,如果兼容IE 8以下再考虑第二种,做兼容方式的书写。

二、oncontextmenu

属于鼠标事件,鼠标右键点击即触发。

用法:

我在document上绑定了此事件,通过事件冒泡机制触发。

document.oncontextmenu =function (){
    alert('触发了);
}

3、事件对象event

概念:

Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。
事件通常与函数结合使用,函数不会在事件发生前被执行

  • 标准浏览器中作为事件回调函数的第一个参数
        document.addEventlistener('contextmenu',function(ev){
             alert(ev)
    })
  • 低版本的IE作为window对象的一个属性
        document.attachEvent('contextmenu',function(){
             alert(window.event)
    })

一般如果不兼容ie 8以下,用标准下的event即可

4、取消默认行为

event.preventDefault()

5、阻止冒泡

event.stopPropagation()

说明:

return false既可以阻止默认行为,也可以阻止冒泡

6、获取滚动距离

  • chrome:

document.body.scrollTop(scrollLeft)

  • 非chrome:

document.documentElement.scrollTop(scrollLeft)

  • 兼容写法:
      var scrollTop = document.documentElement.scrollTop||document.body.scrollTop,
        var scrollLeft = document.documentElement.scrollLeft||document.body.scrollLeft,

7、clientX(Y)

它提供事件发生时的应用客户端区域的水平坐标 (与页面坐标不同)。例如,当你点击客户端区域的左上角时,鼠标事件的 clientX 值为 0 ,这一值与页面是否有水平滚动无关

也就是说到浏览器可视窗口的距离(不包括浏览器的工具栏)

8、 获取元素具体尺寸

  • offsetHeight/offsetWidth

通常,元素的offsetHeight是一种衡量标准,包括元素的边框、垂直(水平)内边距和元素的水平(垂直)滚动条(如果存在且渲染的话)和元素的CSS高度(宽度)

注意:

clientWidth/clientHeight的区别是,`clientWidth/clientHeight 不包括边框,和垂直(水平)滚动条的宽度(高度)

9、获取浏览器视口的尺寸

    var   browserHeight = document.documentElement.clientHeight,//浏览器视口的高度
                    browserWidth = document.documentElement.clientWidth;
注意:

一开始使用了window.innerWidth/window.innerHeight 来获取视口尺寸,结果在极限情况下被滚动条覆盖一部分,后来找到原因是: window.innerWidth/window.innerHeight 是把滚动条也算在内的

10、类数组转换成数组

Array.prototype.slice.call(类数组,0)

11、自定义右键菜单实现思路

1、让自定义菜单相对于浏览器视口做绝对定位通过改变left/top值来改变每次菜单的位置
2、取消原来的右键菜单默认行为
3、考虑极限情况,判断event.clientX+菜单的offsetWidth/event.clientY+offsetHeight是否大于等于  document.documentElement.clientWidth/document.documemtElement.clientHeight ,如果大于等于,则把left/top赋值为 event.clientX-菜单的offsetWidth/event.clientY-菜单的offsetHeight,否则赋值为event.clientX/event.clientY

js代码:
    window.onload = function (){
  var oClick = document.getElementById('click_region'),
      oMenu = document.getElementById('menu'),
      aLi = oMenu.getElementsByTagName('li'),
      browserHeight = document.documentElement.clientHeight,//浏览器视口的高度
      browserWidth = document.documentElement.clientWidth; //浏览器视口的快读,不包括垂直滚动天的宽度

  document.oncontextmenu = function (ev){
    oMenu.style.display = 'block';
    var ev = ev||window.event,
        scrollTop = document.documentElement.scrollTop||document.body.scrollTop,
        scrollLeft = document.documentElement.scrollLeft||document.body.scrollLeft,
        clientX = ev.clientX,
        clientY = ev.clientY,
        // 注意:只有在隐藏的元素变成display:block的状态才能获取他的宽度和高度
        offsetWidth = oMenu.offsetWidth,
        offsetHeight = oMenu.offsetHeight,
        top,
        left;

    if(clientY+offsetHeight>=browserHeight){
       top = clientY-offsetHeight
    }else{
      top = clientY
    }

    if(clientX+offsetWidth>=browserWidth){
      left = clientX-offsetWidth
      console.log(left);
    }else{
      left = clientX
    }

    oMenu.style.left = left+'px';
    oMenu.style.top =scrollTop+top+'px';
    return false//阻止默认行为,并且阻止冒泡
  }

  // 取消自定义菜单
  document.onclick = function (){
      oMenu.style.display = 'none';
  }

    var lis = Array.prototype.slice.call(aLi,0); //类数组转成数组


    //遍历数组
    lis.forEach(function (item,index,arr){
      aLi[index].onclick = function (event){
        alert(this.innerHTML)
         event.stopPropagation();
      }
    })

}
学习前端的过程中,我整理了很多资料,也希望能共享出来帮助到更多刚接触或者接触前端不久的同学。不过也为了把控微信群的质量,入群的一定要是前端的小伙伴才可以。入群我就会把资料发给每个人,每天也会挑选前沿的前端高质量文章发到群里给大家学习。想加入的同学可以加微信:iamaixiaoxiao,拉你入群。再次强调,保证群高质量,群非前端不加,请谅解哦。扫描微信二维码也可以。