Skip to content

事件

事件流

事件冒泡与事件捕获

1、事件冒泡(IE事件流)

由事件点击的具体元素逐级向上接受,直到Document (DOM树的上下级,非显示上下级)
div -> body -> html -> document

1、事件捕获(网景)

从document开始 逐级触发 到 触发元素
document -> html -> body -> div

3、DOM事件流

先捕获 -> 处于目标阶段 -> 冒泡阶段
浏览器支持 : IE9+ chrome Opera safari firefox

事件处理方式

分为DOM0、DOM2、IE

1、DOM0

el.onclick = fn;

2、DOM2

el.addEventListener('click',handler, 是否捕获)
el.removeEventListener('click',handler, 是否捕获)
浏览器支持 : IE9+ chrome Opera safari firefox

3、IE事件处理方法

el.attachEvent('onclick',hander); // 只支持冒泡
el.detachEvent('onclick',hander); // 只支持冒泡
浏览器支持 : IE

重点:

  • 1、 浏览器支持 所有的浏览器都支持 el.onclick = fn 的DOM0的方式; chrome、firefox、 IE9+ 都支持addEventListener , 但是IE8-不支持; IE10-都支持attachEvent ,但是chrome、firefox、IE11+不支持attachEvent。
  • 2、 el.onclick = fn 的方式 导致元素支持绑定一个触发函数,而attachEvent,addEventListener支持绑定多个触发函数。
  • 3、el.attachEvent() 不支持 2种事件流 支持默认IE的冒泡流,所以addEventListener() 第三个参数需要设置成false
  • 4、触发函数中this的指向 onclick 和addEventListener 都指向 元素本身; attachEvent指向Window(需要修改)
var div3 = document.getElementById("div3");

var fn3 = function(e){
    console.log(3);
}
var fn2 = function(e){
    console.log(2);
}
div3.onclick = fn3;
div3.onclick = fn2;   //结果只触发  2;测试  所有的浏览器都支持

div3.addEventListener('click',fn3,false);
div3.addEventListener('click',fn2,false);    // 发现 先触发3 在触发2
// chrome、firefox、 IE9+ 都支持  , 但是IE8-就报 对象不支持“addEventListener”属性或方法

div3.attachEvent('onclick',fn3);  
div3.attachEvent('onclick',fn2);    // 发现 先触发3 在触发2
// IE10-都支持  , 但是就报 chrome、firefox、 IE11+对象不支持“addEventListener”属性或方法

跨浏览器事件处理方式

(function() {
    var utils = utils || {};

    if (document.body.addEventListener) {
        utils.addEvent = function(el, type, handler) {
            el.addEventListener(type, function(e) {
                handler.call(this, e);
            }, false)
        }
    } else if (document.body.attachEvent) {
        utils.addEvent = function(el, type, handler) {
            el.attachEvent('on' + type, function(e) {
                e.target = e.target || e.srcElement;
                handler.call(el, e);
            })
        }
    } else {
        utils.addEvent = function(el, type, handler) {
            el['on' + type] = handler;
        }
    }
    // 取消事件
    if (document.body.removeEventListener) {
        utils.removeEvent = function(el, type, handler) {
            el.removeEventListener(type, handler, false)
        }
    } else if (document.body.detachEvent) {
        utils.removeEvent = function(el, type, handler) {
            el.detachEvent('on' + type, handler)
        }
    } else {
        utils.removeEvent = function(el, type, handler) {
            el['on' + type] = null;
        }
    }
    // 阻止事件的默认行为
    utils.stopDefault = function(e) {
        if (e && e.preventDefault) {
            e.preventDefault();
        } else {
            e.returnValue = false;
        }
        // 
        return false;
    }

    // 阻止事件的冒泡或者捕获 等进一步行为
    utils.stopPropagation = function(e) {
        if (e && e.stopPropagation) {
            e.stopPropagation();
        } else {
            e.cancelBubble = true;
        }
    }
    window.utils = utils;
})()

事件对象event

ie/chrome : event是一个全局的对象

console.log(event); // chrome :  undefined  / ie : null
  • type 事件的类型(不区分大小写)。
  • event.target | IE 为 event.srcElement Element 事件的目标。
  • event.currentTarget 事件处理程序中当前正在处理事件的那个元素。 ***
  • event.bubbles Boolean 表明事件是否冒泡;
  • event.stopPropagation() 取消事件的进一步捕获或者冒泡; event.bubbles === true; IE event.cancelBubble = true;
  • event.cancelable Boolean 表明是否可以取消事件的默认行为
  • event.preventDefault() 取消事件的默认行为; event.cancelable === true; IE event.returnValue = false;
<font class="j-font-red j-font-blod" >注意:</font>
1. 事件默认行为与事件流 是两个概念,一个元素可以同时触发这两件事,比如 a标签可以定义点击事件和href链接跳转,那么我们点击的时候就会先触发事件,再触发默认行为;
2. 事件流虽然分为事件冒泡和事件捕获两种 但是IE只支持事件的冒泡 所以我们常用的都是事件冒泡来处理方法;

var div2 = document.getElementById('div2');

div2.addEventListener('click',function(event){
    console.log(event);
    console.log(event.type);          //click
    console.log(event.target);        // button#div3   // 点击触发的元素
    console.log(event.currentTarget); // div#div2  绑定事件的元素
},false)

utils.addEvent(div2,'click',function(e){
    console.log(e);
    console.log(e.target.tagName);   // BUTTON
})

utils.addEvent(document.getElementById("a1"),'click',function(e){
    console.log('触发了默认的行为');
    console.log(e);   // BUTTON
})

var btn1 = document.getElementById("btn1");

utils.addEvent(btn1,'click',function(e){
    console.log(e);

    // 鼠标事件 属性
    // e.clientX/e.clientY 
    // 返回事件发生时的应用客户端区域的水平坐标/垂直坐标 (与页面坐标不同)
})

事件的类型

  • 1、UI事件 load、unload、about、error、select、resize、scroll
  • 2、焦点事件
  • 3、鼠标事件
  • 4、滚轮事件
  • 5、文本事件
  • 6、键盘事件
  • 7、合成事件

1、UI事件

1.1、load事件

资源加载后触发load事件,其中一般包含 window script style img

javascript

utils.addEvent(window, 'load', function(e) {
    console.log('load事件-----')
    console.log(e);
})

//如我们常见的加载图片
var loadImg = document.createElement('img');
loadImg.src = "../Node类型/childNodes1.png";
utils.addEvent(loadImg, 'load', function(e) {
    console.log('图片已经加载完成...');
    console.log(e)
})
document.getElementById('load').appendChild(loadImg);
// 会输出 图片已经加载完成..
// 会输出 load事件----

1.2 unload事件

资源卸载后触发unload事件,其中一般包含 window script style img

<font class="j-font-red j-font-blod" >注意:</font>
1. 请小心编写onunload事件处理函数,因为资源已经卸载,所以页面存在中的元素不一定存在,特别是window的onunload处理函数。

1.3 resize事件

当浏览器窗口调整到一个新的高度或者宽度时触发。

<font class="j-font-red j-font-blod" >注意:</font>
1. 何时触发resize,不同浏览器不同,IE、chrome..会在浏览器窗口变化1px的时候就触发,firefox会在用户停止调整窗口的时候触发,所以不要在处理程序中加入大量代码,浏览器最小化最大化也会触发,,,建议使用 事件防抖debounce 或者 requestAnimationFrame。
javascript

utils.addEvent(window, 'resize', function(e) {

    // 当然也不要 在function里面添加requestAnimationFrame获取防抖处理函数,因为
    var raf = requestAnimationFrame(function(e1) {
        console.log('浏览器窗口修改了');
        console.log(e);
        //获取浏览器可视区的大小
        console.log(document.documentElement.clientWidth + 'px');
        console.log(document.documentElement.clientHeight + 'px');
    })

})


utils.addEvent(window, 'resize', raf);

1.4 scroll 事件

文档或者元素进行滚动的时候触发。

<font class="j-font-red j-font-blod" >注意:</font>
1.在文档滚动期间重复触发。
javascript
utils.addEvent(window, 'scroll', function(e) {
    console.log('文档滚动了');
    console.log(e);
    // 返回事件相对于整个文档的水平坐标。
    console.log(e.pageX);
    //返回事件相对于整个文档的垂直坐标。  只有firefox有效 所以没用
    console.log(e.pageY);
    // 文档滚动的高度
    console.log(document.documentElement.scrollTop);
})
utils.addEvent(document.getElementById('scroll'), 'scroll', function(e) {
    console.log('scroll元素滚动了');
    console.log(this.id);
    // 元素滚动的高度
    console.log(this.scrollTop);
})

1.5 select 事件

用户选择了文本 或者 input/textarea调用了select() 方法。

<font class="j-font-red j-font-blod" >注意:</font>
1.IE9+/其他 只有用户选择了文本并且释放鼠标才触发; IE8- 只要用户选择了一个字母不必释放鼠标就触发。
javascript
utils.addEvent(document.getElementById("select1"), 'select', function(e) {
    console.log("select1选择了文本");
    console.log(e);
})
utils.addEvent(document.getElementById("select2"), 'click', function(e) {
    document.getElementById("select2value").select();
})
//  chrome  触发了 2次
//  IE、firefox 触发了1次
utils.addEvent(document.getElementById("select2value"), 'select', function(e) {
    console.log("select2value选择了文本");
    console.log(e);
    console.log(e.target.selectionStart + ' ---' + e.target.selectionEnd);

    if (typeof e.target.selectionStart === 'number') {
        console.log(e.target.value.substring(e.target.selectionStart, e.target.selectionEnd)) //这是默认的文本 IE9+/其他
    } else {
        console.log(document.selection.createRange().text); // IE8-
    }
})

2、焦点事件

  • blur()
  • focus()
  • focusin()
  • focusout()

2.1 focus 事件

元素获得焦点时触发

<font class="j-font-red j-font-blod" >注意:</font>
1. 对于 window、document.documentElement、document.body 无效。
2. 事件不会冒泡。
javascript
utils.addEvent(window, 'focus', function(e) {
    console.log('HTML元素获得焦点了');
    console.log(e);
})
utils.addEvent(document.getElementById("focus1"), 'focus', function(e) {
    console.log('focus1元素获得焦点了');
    console.log(e);
})

2.2 blur 事件

元素获得失去焦点时触发

<font class="j-font-red j-font-blod" >注意:</font>
1. 对于 window可以判断文档是否失去焦点,document.documentElement、document.body 无效。
2. 事件不会冒泡。
javascript
utils.addEvent(window, 'blur', function(e) {
    console.log('HTML元素获得焦点了');
    console.log(e);
})
utils.addEvent(document.getElementById("focus1"), 'blur', function(e) {
    console.log('focus1元素获得焦点了');
    console.log(e);
})

2.3 focusin 事件

元素获得失去焦点时触发

<font class="j-font-red j-font-blod" >注意:</font>
1. 对于 window可以判断文档是否失去焦点,document.documentElement、document.body 无效。
2. 事件会冒泡。

2.4 focusout 事件

元素获得失去焦点时触发

<font class="j-font-red j-font-blod" >注意:</font>
1. 对于 window可以判断文档是否失去焦点,document.documentElement、document.body 无效。
2. 事件会冒泡。
javascript
utils.addEvent(window, 'blur', function(e) {
    console.log('HTML元素获得焦点了');
    console.log(e);
})
utils.addEvent(document.getElementById("focus"), 'focusin', function(e) {
    console.log('focus元素获得focusin焦点');
})
utils.addEvent(document.getElementById("focus1"), 'focusin', function(e) {
    console.log('focus1元素获得focusin焦点');
})
utils.addEvent(document.getElementById("focus"), 'focus', function(e) {
    console.log('focus元素获得focus焦点');
})
utils.addEvent(document.getElementById("focus1"), 'focus', function(e) {
    console.log('focus1元素获得focus焦点');
})

// chrome firefox   focus1元素获得focus焦点 -> focus1元素获得focusin焦点 -> focus元素获得focusin焦点
// IE               focus1元素获得focusin焦点 -> focus元素获得focusin焦点 -> focus1元素获得focus焦点
//  可以发现  非IE浏览器 先focus焦点事件触发  再focusin触发 -> 再冒泡 focusin触发
//      IE 浏览器  是先 focusin 触发 -> 再冒泡 focusin触发 -> 最后 focus焦点事件触发


// 两个元素间的 焦点获得与失去 顺序
var foucs1 = document.getElementById("focus1"),
    focus2 = document.getElementById("focus2");
utils.addEvent(foucs1, 'focusin', function(e) {
    console.log('foucs1元素获得focusin焦点');
})
utils.addEvent(foucs1, 'focusout', function(e) {
    console.log('foucs1元素获得focusout焦点');
})
utils.addEvent(foucs1, 'focus', function(e) {
    console.log('foucs1元素获得focus焦点');
})
utils.addEvent(foucs1, 'blur', function(e) {
    console.log('foucs1元素获得blur焦点');
})

utils.addEvent(focus2, 'focusin', function(e) {
    console.log('focus2元素获得focusin焦点');
})
utils.addEvent(focus2, 'focusout', function(e) {
    console.log('focus2元素获得focusout焦点');
})
utils.addEvent(focus2, 'focus', function(e) {
    console.log('focus2元素获得focus焦点');
})
utils.addEvent(focus2, 'blur', function(e) {
    console.log('focus2元素获得blur焦点');
})
// 一个元素从获得到失去焦点
// chrome,firefox    focus -> focusin -> blur -> focusout
// IE                focusin -> focus -> focusout -> blur
// 一个元素到里一个元素
// chrome,firefox     blur -> focusout -> focus -> focusin
// IE                 focusout ->  focusin -> blur -> focus
//

<font class="j-font-red j-font-blod" >重点:</font>

1. focus()、blur()不会触发事件冒泡, focusin()、focusout() 会触发事件冒泡。
2. 元素从获得到失去 在IE和其他都不一样 如上面。
3. 元素从一个元素到另一个元素 也不一样。
4. 建议要么用focus、blur 要么用focusin focusout 不要合用。

鼠标和滚轮事件

  • click() 用户鼠标点击或者回车键触发 冒泡
  • dbclick() 双击事件 冒泡
  • mousedown() 用户按下鼠标按钮是触发 冒泡
  • mouseup() 用户释放鼠标按钮时触发 冒泡
  • mouseenter() 鼠标光标从元素外部首次移入元素时触发,移动到后代元素也不会触发。 不冒泡
  • mouseleave() 鼠标光标从元素上首次移到元素范围之外时触发,移动到后代元素也不会触发。 不冒泡
  • mousemove() 当鼠标在元素内部移动是重复触发,即元素移动到后代也会触发 冒泡
  • mouseout() 当鼠标从元素上方移动到其他元素时触发,到子元素也触发 冒泡
  • mouseover() 当鼠标位于一个元素的外部,然后首次移入元素之内触发 冒泡
鼠标触发顺序 mousedown() -> mouseup() -> click() -> mousedown() -> mouseuo() -> click() -> dbclick()
鼠标事件继承于MouseEvents接口
  • e.clientX e.clientY 保存鼠标指针在视口区的水平和垂直坐标
  • e.pageX e.pageY 保存鼠标指针在页面水平和垂直坐标 IE8-不支持
  • e.screenX e.screenY 保存鼠标指针在整个浏览器屏幕水平和垂直坐标

<font class="j-font-red j-font-blod" >重点:</font>

1. 我们常用的自定义下拉菜单等移入展示,移出隐藏 一般使用 mouseenter mouseleave (最常用)。
2.记住鼠标事件的 视口距离 页面文档距离 屏幕坐标。
var button1 = document.getElementById("mouse1");

utils.addEvent(button1, 'click', function(e) {
    console.log(e);
    // 当前鼠标 在 视口 的水平距离
    console.log("clientX = " + e.clientX);
    // 当前鼠标 在 视口 的垂直距离
    console.log("clientY = " + e.clientY);
    // 鼠标指针 距离页面文档 的水平距离 IE8-不支持
    console.log("pageX = " + (e.clientX + document.documentElement.scrollLeft))
    console.log("pageX = " + e.pageX);
    // 鼠标指针 距离页面文档 的垂直距离 IE8-不支持
    console.log("pageY = " + (e.clientY + document.documentElement.scrollTop))
    console.log("pageY = " + e.pageY);
    // 鼠标指针 距离浏览器 的水平距离
    console.log("screenX = " + e.screenX);
    // 鼠标指针 距离浏览器 的垂直距离
    console.log("screenY = " + e.screenY);
    // 当前鼠标 在 视口 的水平距离
    console.log("x = " + e.x);
    // 当前鼠标 在 视口 的垂直距离
    console.log("y = " + e.y);

    // 修改键
    console.log(e.shiftKey);  // 是否按下 shift键  按下为true    IE8-不支持
    console.log(e.ctrlKey);  // 是否按下 ctrl键
    console.log(e.altKey);  // 是否按下 alt键
    console.log(e.metaKey);  // 是否按下 meta键

    // 鼠标按钮
    console.log(e.button)   //鼠标按钮   数字
<!--})-->

键盘和文本事件

  • keydown() 用户按下键盘任意键时触发,按住不放会重复触发
  • keypress() 用户按下键盘上的字符键时触发,持续按住重复触发
  • keyup() 用户释放键盘按键触发
  • textInput() 在可编辑去输入字符时才会触发 与keypress 不同 可获得焦点的就可以触发keypress 但是只有可编辑区域才能触发textInput 且实际输入字符 ie9+ Safari Chrome支持 其他不支持 基本没用

<font class="j-font-red j-font-blod" >重点:</font>

1. 不是鼠标放在上面就可以又条件触发事件,是元素必须获得焦点。
2. 记住鼠标事件的 视口距离 页面文档距离 屏幕坐标。
javascript
var key1 = document.getElementById("key1"),   //button
    key2 = document.getElementById("key2");   //input

utils.addEvent(key1,'keypress',function(e){
    //
    console.log('button is keypress ');
    console.log(e);
    console.log(e.keyCode);   //13
})

HTML5事件

  • contextmenu() 用户上下文菜单(右键显示菜单) 鼠标事件、在元素上
  • beforeunload() 用户卸载页面前触发事件,可以阻止页面的关闭
  • DOMContentLoaded() 文档形成完整的DOM数之后就触发,不理会图像,script,link等资源 IE9+
  • readystatechange() 文档或者元素加载状态的信息
javascript
var readystatechange1 = document.getElementById("readystatechange1");

utils.addEvent(window,'DOMContentLoaded',function(e){
    console.log('DOMContentLoaded');
})
utils.addEvent(window,'load',function(e){
    console.log('load');
})
utils.addEvent(document,'readystatechange',function(e){
    console.log('readystatechange');
    console.log(document.readyState);
})
var insertImg = document.createElement('img');
insertImg.src= 'https://note.youdao.com/yws/public/resource/d4e10a6cea85cbf9da7dfd3ee06141f3/xmlnote/6D1107C647ED46609EB70AF97FAAA500/3473';
utils.addEvent(insertImg,'readystatechange',function(e){
    console.log('image is readystatechange');
    console.log(insertImg.readyState);
})
utils.addEvent(insertImg,'load',function(e){
    console.log('image is load');
})
utils.addEvent(insertImg,'DOMContentLoaded',function(e){
    console.log('image is DOMContentLoaded');
})
document.getElementById("html5").appendChild(insertImg);

readystatechange

其元素上有一个readyState属性

  • uninitialized(未初始化) :对象存在但尚未初始化
  • loading(正在加载中)
  • loaded(加载完毕)
  • interactive(交互)
  • compltet(完成)
chrome : readystatechange(interactive) -> DOMContentLoaded -> readystatechange(complete) -> load
IE 支持 加载元素 的readystatechange事件 readystatechange(interactive) -> DOMContentLoaded -> image readystatechange(loading) -> image load -> readystatechange(complete) -> load -> image readystatechange(complete)
先出现 readystatechange-> DOMContentLoaded -> load

触摸手势事件

  • touchstart()
  • touchmove()
  • touchend()
  • touchcancel()

touchstart()

当手指触摸屏幕时触发,即使已有手指放在屏幕也会触发。

event对象

bubbles cancelable view clientX clientY screenX screenY detail altKey shiftKey ctrlKey metaKey
  • touches : 表示当前跟踪的触摸操作的Touch对象的数组
  • targetTouchs : 特定于事件目标的Touch对象的数组
  • changeTouches : 表示 自上次触摸以来发生了改变的Touch对象的数组

每一个touch对象都是由下列信息组成

  • clientX/clientY // 触摸点相对于浏览器窗口的位置
  • pageX/pageY // 触摸点相对于页面的位置
  • screenX/screenY //触摸点相对于屏幕的位置
  • identifier // touch对象的ID
  • target // 触发的DOM对象
  • radiusX,radiusY,rotationAngle // 移动端点击是手指形状的椭圆形,此为椭圆的两个半径和角度
<font class="j-font-red j-font-blod" >注意:</font>
1.手指在滑动整个屏幕时,会影响浏览器的行为,如滚动或缩放,所以记得取消元素的默认行为。
  • 1.1 禁止缩放
<meta name="viewport" content="target-densitydpi=320,width=640,user-scalable=no">
  • 1.2 禁止滚动
preventDefault是阻止默认行为,touch事件的默认行为就是滚动。 event.preventDefault();
javascript

var x = {};
utils.addEvent(document.getElementById("touch1"),'touchstart',function(e){
    console.log('touchstart');
    console.log(e);
    x = e.targetTouches[0];
    console.dir(e.targetTouches[0]);   
    // { clientX:125.44000244140625,clientY:614.1329956054688,pageX:125.44000244140625,pageY:614.1329956054688,screenX:2501,screenY:393,identifier:0,
    // radiusX:30.053333282470703,radiusY:30.053333282470703,force:1,rotationAngle:0} 
})
utils.addEvent(document.getElementById("touch1"),'touchmove',function(e){
    console.log('touchmove');
    console.log(e);
})
utils.addEvent(document.getElementById("touch1"),'touchend',function(e){
    console.log('touchend');
    console.log(e);
})

事件委托

什么是事件委托

js高级程序设计说: 利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件 jQuery中使用 $().on('click...','子元素',function(){})

<font class="j-font-red j-font-blod" >注意:</font>
1. 支持冒泡的事件才可以代理,触发元素绑定同样的事件的时候不能阻止事件冒泡。
2. 通过 e.target || e.srcElement 获取 触发元素不一定是 指定的子元素 可能是其子元素。
javascript
utils.addEvent(document.getElementById("onul"),'click',function(e){
    // console.log(e.target || e.srcElement)   //<p>这是列1</p>
    // 我们就可以通过e.target来判断 那个p触发了 点击事件 然后分别处理,
    // 但是我们存在一个问题  我们需要的li而不是其子元素p
    var target = e.target || e.srcElement;
    while(target.tagName.toLowerCase() !== 'li' && target.id !== 'onul'){
        target = target.parentNode;
    }
    console.log(target);
    // 当然这只是简易的 
})

事件学习源码