js 事件示例代码及相关补充

163 阅读4分钟

事件相关浏览器兼容性代码,各种代码事件示例,右键菜单、拖拽功能、事件委托、模拟事件等代码示例……

一 一般事件行为的兼容性代码

/** 
 * 跨浏览器事件处理方法
 * 注:只关注冒泡阶段 
 */
var EventUtil = {
  // 添加事件
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {
      // DOM2 添加事件
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      // IE 添加事件
      element.attachEvent('on' + type, handler);
    } else {
      // DOM0 添加事件
      element['on' + type] = handler;
    }
  },
  // 删除事件
  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      // DOM2 删除事件
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      // IE 删除事件
      element.detachEvent('on' + type, handler);
    } else {
      // DOM0 删除事件
      element['on' + type] = null;
    }
  },
  // 获取事件对象
  getEvent: function(e) {
    return e ? e : window.e;
  },
  // 获取事件目标(DOM)
  getTarget: function(e) {
    return e.target || e.srcElement;
  }
  // 阻止事件默认行为
  preventDefault: function(e) {
    if (e.preventDefault) {
        e.preventDefault();
    } else {
        e.returnValue = false;
    }
  },
  // 阻止冒泡
  stopPropagation: function(e) {
    if (e.stopPropagation) {
        e.stopPropagation();
    } else {
        e.cancelBubble = true;
    }
  }
}

二 scroll 事件示例

滚动带滚动条的元素,在该元素中触发。可通过元素的 scrollLeft / scrollTop 来监听滚动

var div = document.getElementById('myDiv');
EventUtil.addHandler(div, 'scroll', function(e){
    console.log(div.scrollTop);
})

如果是在 window 对象上:

EventUtil.addHandler(window, 'scroll', function(e){
    if (document.compatMode == 'CSS1Compat') {
        console.log(document.documentElement.scrollTop);
    } else {
        console.log(document.body.scrollTop);
    }
})

三 获取鼠标位置

var div = document.getElementById('myDiv');
// 浏览器视口鼠标坐标
EventUtil.addHandler(div, 'click', function(e){
    e = EventUtil.getEvent(e);
    console.log(e.clientX, e.clientY);
})

// 页面中鼠标坐标,包含滚动条卷起的部分
EventUtil.addHandler(div, 'click', function(e){
    e = EventUtil.getEvent(e);
    var pageX = e.pageX, 
        pageY = e.pageY;
    
    // 为了兼容IE8
    if (pageX === undefined) {
        pageX = e.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
    }
    if (pageY === undefined) {
        pageY = e.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
    }

    console.log(e.clientX, e.clientY);
})

// 屏幕中鼠标坐标
EventUtil.addHandler(div, 'click', function(e){
    e = EventUtil.getEvent(e);
    console.log(e.screenX, e.screenY);
})

四 鼠标滚轮兼容性代码

// 鼠标滚轮值
EventUtil.getWheelDelta = function(e) {
    if (e.wheelDelta) {
        // 向前一次+120, Opera9.5- 浏览器相反
        return (client.engine.opera && client.engine.opera < 9.5 ? -e.wheelDelta : e.wheelDelta);
    } else {
        // Firefox 向滚一次是-3,乘以40与其他浏览器保持一致
        return -e.detail * 40;
    }
}

五 获取键盘键码

EventUtil.getCharCode = function(e) {
    if (typeof e.charCode === 'number') {
        return e.charCode;
    } else {
        return e.keyCode;
    }
}

六 右键菜单

<div id="myDiv" style="position:absolute;display:none;">右键这里</div>
<ul id="myMenu">
    <li>菜单1</li>
    <li>菜单2</li>
    <li>菜单3</li>
</ul>

var div = document.getElementById('myDiv');
EventUtil.addHandler(div, 'contextmenu', function(e){
    // 阻止默认行为
    e = EventUtil.getEvent(e);
    EventUtil.preventDefault();
    // 展示位置
    var menu = document.getElementById('myMenu');
    menu.style.left = e.clientX + 'px';
    menu.style.top = e.clientY + 'px';
    menu.style.display = 'inline';
});
// 左键点击菜单消失
EventUtil.addHandler(document, 'click', function(e){
    document.getElementById('myMenu').style.display = 'none';
});

七 阻止右键菜单及选中内容

可以阻止“审查元素”及复制页面内容。

// 注意:可在控制台上运行 document.oncontextmenu="" 破解
document.oncontextmenu = new Function("event.returnValue=false");
document.onselectstart = new Function("event.returnValue=false");
document.querySelector("body").onselectstart = function() {
    return false;
};

通过 css 也可以阻止选中页面内容。

* {
    moz-user-select: -moz-none;
    -moz-user-select: none;
    -o-user-select:none;
    -khtml-user-select:none;
    -webkit-user-select:none;
    -ms-user-select:none;
    user-select:none;
}

以上,包括有阻止 ctrl + c 快捷键方式的,均无法阻止调用开发者工具,然后复制 dom 的行为。

八 事件内存、性能原理及代码

添加到页面上的事件处理程序越多,内存中的对象就越多,造成页面渲染和就绪时间延迟、事件不灵敏等性能问题。可以通过“事件委托”和适时移除事件的方式减少内存消耗,优化性能。

1. 事件委托

利用事件冒泡的机制,指定一个事件处理程序来管理同一类型的所有事件。

<ul id="links">
    <li id="doSomething">doSomething</li>
    <li id="goSomewhere">goSomewhere</li>
    <li id="sayHi">say hi</li>
</ul>

var list = document.getElementById('links');
EventUtil.addHandler(list, 'click', function(e){
    e = EventUtil.getEvent(e);
    var target = EventUtil.getTarget(e);

    switch(target.id) {
        case 'doSomething':
            document.title = 'the document’s title was changed';
            break;
        case 'goSomewhere':
            location.href = 'http://www.baidu.com';
            break;
        case 'sayHi':
            alert('hi');
            break;
    }
})

适合采用事件委托的事件包括:click / mousedown / mouseup / keydown / keyup / keypress。

2. 移除事件

元素被删除或替换时,原来的事件并没有删除,而是继续滞留在内存里,造成内存的损耗。在处理 DOM 或卸载页面时,应该注意删除相关的事件。

九 事件调试

在 Chrome 浏览器开发者工具中,可以查看 DOM 绑定的事件。

<div><button id="btn1">Click1</button></div>
<script>
    var btn = document.getElementById('btn1');
    btn.addEventListener('click', function(){
        console.log('click one');
    }, false);

    btn.addEventListener('click', function(){
        console.log('click two');
    }, false);
</script>

click1.png

Click1 按钮绑定两次点击事件,开发者工具中可以查看,点击一次两个事件均会触发。

基于 jQuery 的事件绑定:

<div><button id="btn2">Click2</button></div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
    $('#btn2').on('click', function(){
        console.log('click one');
    })
    $('#btn2').on('click', function(){
        console.log('click two');
    })
    $('#btn2').on('mousemove', function(){
        console.log('mousemove');
    })
    $('#btn2').on('mouseenter', function(){
        console.log('mouseenter');
    })
    // jQuery 事件的卸载
    // $('#btn2').off();
</script>

click2.png

由图可见,Click2 按钮绑定了两次 click 事件,但开发者工具中只显示了一个,实际点击后,仍会触发两次,这里要注意。使用off方法,不传入参数,能将该 DOM 所有绑定的事件清除掉。