13. JS 事件机制

一、事件流

  • 事件冒泡

  • 事件捕获

二、事件流阻止

三、绑定事件处理函数的方法

四、事件对象 event

  • 事件类型 --> type

五、事件对象 event 属性详解

六、举例

一、事件流

(1). 事件冒泡

当一个元素上的事件被触发的时候(比如说鼠标点击了一个按钮),同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。

通俗来讲,冒泡指的是:子元素的事件被触发时,父盒子的同样的事件也会被触发。取消冒泡就是取消这种机制。

冒泡顺序:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>事件流</title>
    
</head>
<body>
<div class='box1' id='box1'>
    <div class='box2'>
        <div class='box3'>
        </div>
    </div>
</div>
</body>
</html>
//事件冒泡
box3.onclick = function () {
    alert("child");
}

box2.onclick = function () {
    alert("father");
}

box1.onclick = function () {
    alert("grandfather");
}

document.onclick = function () {
    alert("body");
}

  • 一般的浏览器(除IE6.0之外的浏览器): div -> body -> html -> document -> window

  • IE6.0:div -> body -> html -> document

(2). 事件捕获

事件捕获的思想是不太具体的 DOM 节点应该更早接收到事件,而最具体的节点最后接收到事件。

按照事件捕获的思想,上面的实例,click 事件将会以如下的顺序逐级传播:

  • document -> html -> body -> div -> input

//参数为true,代表捕获;参数为false或者不写参数,代表冒泡
box3.addEventListener("click", function () {
    alert("捕获 child");
}, true);

box2.addEventListener("click", function () {
    alert("捕获 father");
}, true);

box1.addEventListener("click", function () {
    alert("捕获 grandfather");
}, true);

document.addEventListener("click", function () {
    alert("捕获 body");
}, true);

(3). DOM 事件流

事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:事件捕获阶段(capture phase)、处于目标阶段(target phase)和事件冒泡阶段(bubbling phase)

即同时支持冒泡和捕获,在任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达了最具体的元素,然后,再从最具体的元素向上进行事件冒泡,直到到达 document

(4). 触发顺序,先捕获,后冒泡

1. 同一个对象的同一个事件类型上面绑定了一个理函数只能遵循一种处理模型,就是说一个元素绑定了一个事件处理函数,那么这个事件处理函数要么就是事件冒泡的处理模型,要么就是事件捕获的处理模型。

2. 同一个对象的同一个事件类型上面绑定了两个事件处理函数,一个是事件冒泡,一个是事件捕获,触发顺序就是先捕获再冒泡。

实例1:

实例2:

(5). 不是所有的事件都能冒泡

以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。

我们检查一个元素是否会冒泡,可以通过事件的以下参数:

  • event.bubbles

  • 如果返回值为true,说明该事件会冒泡;反之则相反。

box1.onclick = function (event) {
    alert("冒泡 child");

    event = event || window.event;
    console.log(event.bubbles); //打印结果:true
}

二、事件流阻止

(1). 取消冒泡事件

在一些情况下我们需要阻止事件流的传播,也就是阻止事件的向上冒泡。

  • event.stopPropagation() -–> W3C标准(IE9以下不支持)

  • event.cancelBubble = true --> IE独有

封装一个函数取消事件冒泡(兼容版):

function stopBubble(event) {
   if (event.stopPropagation) {
       event.stopPropagation();
   } else {
       event.cancelBubble = true;
   }
}

(2). 阻止默认事件

默认事件:表单提交,a标签跳转或者刷新页面,点击右键弹出菜单..........

  • return false —> 以句柄(elem.onXXX = fn)的方式绑定的事件才生效

  • event.prevetDefault() —> W3C标注,IE9以下不兼容

  • event.returnValue = false —> 兼容IE

封装阻止默认事件的函数(兼容版):

function cancelHandler(event) {
    if(event.prevetDefault){
        event.prevetDefault();
    }else{
        event.returnValue = false;//包括了兼容IE的,也包括了以句柄形式的return false;
    }
}

(3). 实例:

实例:阻止a标签默认跳转或者刷新页面

  • 点击 a 链接后有一个默认的跳转行为,并且浏览器会认为你点了 a 链接,也就点了其父容器 div 元素。

  • 阻止了事件冒泡和默认行为之后,再点击跳转,就不会有任何响应了

//<div id="box">
//    <a href="http://www.baidu.com" id="go">跳转</a>
//</div>

var oDiv = document.getElementById('box');
var oA = document.getElementById('go');
oDiv.onclick = function (){
    alert('我是a链接的父容器');
}
oA.onclick = function (ev){
    ev.stopPropagation();
    ev.preventDefault();
}

实例:阻止点击右键默认弹出菜单

//第一种方式
	 document.oncontextmenu = function(){
        console.log('a');
        return false;
    }
    
//第二种方式
	 document.oncontextmenu = function (e) {
	     console.log('a');
	     cancelHandler(e);
	 }
    function cancelHandler(event) {
        if(event.prevetDefault){
            event.prevetDefault();
        }else{
            event.returnValue = false;
        }
    }

三、绑定事件处理函数的方法

(1). DOM0级事件处理程序:element.onXXX = function(e){}

  • 兼容性很好,但是一个元素的同一个事件只能绑定一个事件函数。

  • 事件绑定只会在事件冒泡中运行,捕获不行

  • this 指向当前元素

  • 删除事件时,直接让事件等于空。如:oBtn.onclick=null;

// <input type="button" value="按钮" id="btn">

var oBtn = document.getElementById('btn');
oBtn.onclick = function (){
    alert(this.type);   // button
}

实例:只能执行一次的事件

var div = document.getElementById('div1');

div.onclick = function(){
    console.log('a');
    this.onclick = null;
}

(2). DOM2级事件处理程序:

1. IE9以上:elem.addEventListener (type, fn, false);

  • 接收三个参数:要处理的事件名,作为事件处理程序的函数,一个布尔值

  • this 指向 dom 元素本身

  • 通常事件监听都是添加在冒泡阶段,所以添加事件的布尔值为 false

  • 第三个参数设置为true时,即为事件捕获事件阶段。参数设置为 false 或者不设置就是冒泡阶段。

// <input type="button" value="按钮" id="btn">

var oBtn = document.getElementById('btn');

oBtn.addEventListener('click', function (){
    alert('Hello');
}, false);
oBtn.addEventListener('click', function (){
    alert('你好');
}, false);
// 先弹出 'Hello',然后弹出 '你好' 
  • 这种方式最重要的好处就是对同一元素的同一个类型事件做绑定不会覆盖,按照代码添加的顺序依次执行。

// <div id="box" style="height:30px;width:200px;background-color:pink;"></div>

setTimeout(function(){
    box.addEventListener('click',function(){this.innerHTML += '1'},false);    
},16);  // 因为延时,所以添加的比后面的稍晚,故而先执行后面的。

box.addEventListener('click',function(){this.innerHTML += '2'},false);    

//  21 21 21 
  • 删除事件:ele.removeEventListener(type, fn, false);

    • 移除时传入的参数与添加处理程序时使用的参数相同

    • addEventListener()添加的匿名函数将无法移除,可以使用监听函数。

// <div id="box" style="height:30px;width:200px;background-color:pink;"></div>

// 无效
box.addEventListener("click",function(){
    this.innerHTML += '1'
},false);
box.removeEventListener('click',function(){
    this.innerHTML += '1'
},false); 

// 有效,事件被删除
var handle = function(){    // 监听函数
    this.innerHTML += '1'
};
box.addEventListener("click",handle,false);
box.removeEventListener('click',handle,false); 

2. IE9以下:elem.attachEvent ('on' + type,fn);

  • 接受相同的两个参数:事件处理程序名称与事件处理程序函数

  • 第一个参数 'click' --> 'onclick'

  • 只支持事件冒泡

  • this 指向 window

// 让 attachEvent 的 this 指向 dom 元素

div.attachEvent('onclick',function() {
    handler.call(div);
});
function handler() {
//事件处理程序
}
  • 删除事件:ele.detachEvent(‘on’ + type, fn);

    • 移除时传入的参数与添加处理程序时使用的参数相同

    • 添加的匿名函数将无法移除,可以使用监听函数。

给一个dom对象添加事件类型的处理函数,封装兼容函数:

function addEvent(elem, type, handler) {
    if(elem.addEventListener) {
        elem.addEventListener(type, handler, false);
    }else if(elem.attachEvent){
        elem.attachEvent('on' + type, function () {
            handler.call(elem);
        })
    }else{
        elem['on' + type] = handler;
    }
}

四、事件对象 event

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。事件通常与函数结合使用,函数不会在事件发生前被执行!

所有浏览器都支持event对象,但支持的方式不同。

  • 事件对象兼容写法:var event = e || window.event (用于IE);

事件对象其中有两个信息,我们最为常用,分别是typetarget

事件对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有些共有的属性和方法

(1). 事件类型 --> type

  • 事件有很多类型,事件对象中的type属性表示被触发的事件类型

// <div id="box" style="height:30px;width:200px;background:pink;"></div>

//鼠标移入时,显示mouseover;移出时,显示mouseout;点击时,显示click
var oBox = document.getElementById('box');
oBox.onclick = oBox.onmouseout =oBox.onmouseover =function(e){
    e = e || event;
    box.innerHTML = e.type;
}

(2). 事件源 --> target/srcElement

  • 事件源对象就是发生事件的是哪个 DOM 元素。

  • event.target(火狐只有这个)

  • event.srcElement(IE只有这个)

  • chrome这两个都有。

  • 兼容写法:

var handler = function(e){
	 var event = e || window.event;
	 var target = event.target || event.srcElement;
}

实例:

<ul id="box" style="background: lightblue">
    <li class="in" style="height: 30px">1</li>
    <li class="in" style="height: 30px">2</li>
</ul>
box.onclick = function(e){
    e = e || event;
    var target = e.target || e.srcElement;
    e.target.style.backgroundColor = 'pink';
}
box.onmouseout = function(e){
    e = e || event;
    var target = e.target || e.srcElement;
    e.target.style.backgroundColor = 'lightblue';
}

实例:

// <div class = "red"></div>

var red = document.getElementsByClassName('red')[0];
red.onclick = function(e){
	  var event = e || window.event;
	  var target = event.target || event.srcElement;
	  console.log(event);
	  console.log(target);//求事件源对象
}

(3). 事件委托

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation),也叫事件委托

  • 事件代理应用事件目标的target和srcElement属性完成。利用事件代理,可以提高性能及降低代码复杂度
<ul>
	<li>1</li>
	<li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
</ul>
// 1,土办法: 给每一个li绑定点击事件:
var li = document.getElementsByTagName('li');
    var len = li.length;
    for(var i = 0; i < len; i++) {
        li[i].onclick = function (){
            console.log(this.innerText);
        }
    }
    
// 2,事件委托办法:给父元素ul一个人绑定点击事件,利用事件冒泡和事件源对象处理 ,
// 因为每点击一个li,该li就会成为事件源对象,因为li始终在ul里面。
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function (e) {
var event = e || window.event;
var target = event.target || event.srcElement;
  console.log(target.innerHTML);
}

利用事件冒泡和事件源对象进行处理的好处:

  • 性能:不需要循环所有的元素一个个绑定事件(题目如果是一百万个li,上面的土方法就会特别憨)

  • 灵活:当有新的子元素添加将进来时不需要重新绑定事件(当有新的li的时候,土方法就要重新给新的li绑定点击事件,但是利用事件委托就可以不用管,因为你再加多少,它总是在ul里面,点击的时候它就会是target)

封装一个可以使用事件委托的事件绑定函数:

function bindEvent(elem,type,selector,fn){
  if(fn == null){
    fn = selector;
    selector = null;
  }
  elem.addEventListener(type,function(e){
    var target;
    if(selector){
      target = e.target;
      if(target.matches(selector)){
        fn.call(target,e);
      }
    }else{
      fn(e);
    }
  })
}

五、事件对象 event 属性详解

button:声明被按下的鼠标键,整数,1代表左键,2代表右键,0代表中键,如果按下多个键,就把这些值加起来,所以3就代表左右键同时按下

clientX/clientY:事件发生的时候,鼠标相对于浏览器窗口可视文档区域的左上角的位置;(在DOM标准中,这两个属性值都不考虑文档的滚动情况,也就是说,无论文档滚动到哪里,只要事件发生在窗口左上角,clientX和clientY都是 0,所以在IE中,要想得到事件发生的坐标相对于文档开头的位置,要加上document.body.scrollLeftdocument.body.scrollTop)

offsetX,offsetY/layerX,layerY:事件发生的时候,鼠标相对于源元素左上角的位置;

x,y/pageX,pageY:检索相对于父元素鼠标水平坐标的整数;

altKey,ctrlKey,shiftKey等:返回一个布尔值;

  • 可以利用event.ctrlKeyevent.shiftKeyevent.altKey判断是否按下了ctrl键、shift键以及alt键.

  • 测试是否在点击的时候按下了这些修改键:

// <div id="box" style="height:30px;width:300px;background:pink;"></div>

box.onclick=function(e){
    e = e || event;
    box.innerHTML = '';
       if(e.shiftKey){box.innerHTML += 'shiftKey;'}
       if(e.ctrlKey){box.innerHTML += 'ctrlKey;'}
       if(e.altKey){box.innerHTML += 'altKey;'}
       if(e.metaKey){box.innerHTML += 'metaKey;'}
}

keyCode:返回keydown和keyup事件发生的时候按键的代码,以及keypress 事件的Unicode字符;(firefox2不支持 event.keycode,可以用 event.which替代 )

fromElement,toElement:前者是指代mouseover事件中鼠标移动过的文档元素,后者指代mouseout事件中鼠标移动到的文档元素;

cancelBubble:一个布尔属性,把它设置为true的时候,将停止事件进一步起泡到包容层次的元素;(e.cancelBubble = true; 相当于 e.stopPropagation()😉

returnValue:一个布尔属性,设置为false的时候可以组织浏览器执行默认的事件动作;(e.returnValue = false; 相当于 e.preventDefault()😉

attachEvent(),detachEvent()/addEventListener(),removeEventListener:为制定 DOM对象事件类型注册多个事件处理函数的方法,它们有两个参数,第一个是事件类型,第二个是事件处理函数。在

attachEvent()事件执行的时候,this关键字指向的是window对象,而不是发生事件的那个元素;

screenX、screenY:鼠标指针相对于显示器左上角的位置,如果你想打开新的窗口,这两个属性很重要;

六、举例

举例:鼠标跟随

body {
    height: 5000px;
}

img {
    position: absolute;
    padding: 10px 0;
    border: 1px solid #ccc;
    cursor: pointer;
    background-color: yellowgreen;
}
// <img src="" width="100" height="100"/>

//需求:点击页面的任何地方,图片跟随鼠标移动到点击位置。
//思路:获取鼠标在页面中的位置,然图片缓慢运动到鼠标点击的位置。
//  兼容ie67做pageY和pageX;
//  原理:     鼠标在页面的位置 = 被卷去的部分+可视区域部分。
//步骤:
//1.老三步。
//2.获取鼠标在页面中的位置。
//3.利用缓动原理,慢慢的运动到指定位置。(包括左右和上下)

//1.老三步。
var img = document.getElementsByTagName("img")[0];
var timer = null;
var targetx = 0;
var targety = 0;
var leaderx = 0;
var leadery = 0;
//给整个文档绑定点击事件获取鼠标的位置。
document.onclick = function (event) {
    //新五步
    //兼容获取事件对象
    event = event || window.event;
    //鼠标在页面的位置 = 被卷去的部分+可视区域部分。
    var pagey = event.pageY || scroll().top + event.clientY;
    var pagex = event.pageX || scroll().left + event.clientX;

    targety = pagey - 30;
    targetx = pagex - 50;

    //要用定时器,先清定时器
    clearInterval(timer);
    timer = setInterval(function () {
        //为盒子的位置获取值
        leaderx = img.offsetLeft;
        //获取步长
        var stepx = (targetx - leaderx) / 10;
        //二次处理步长
        stepx = stepx > 0 ? Math.ceil(stepx) : Math.floor(stepx);
        leaderx = leaderx + stepx;
        //赋值
        img.style.left = leaderx + "px";


        //为盒子的位置获取值
        leadery = img.offsetTop;
        //获取步长
        var stepy = (targety - leadery) / 10;
        //二次处理步长
        stepy = stepy > 0 ? Math.ceil(stepy) : Math.floor(stepy);
        leadery = leadery + stepy;
        //赋值
        img.style.top = leadery + "px";

        //清定时器
        if (Math.abs(targety - img.offsetTop) <= Math.abs(stepy) && Math.abs(targetx - img.offsetLeft) <= Math.abs(stepx)) {
            img.style.top = targety + "px";
            img.style.left = targetx + "px";
            clearInterval(timer);
        }
    }, 30);
}

举例:获取鼠标距离所在盒子的距离

  • 关键点:鼠标距离所在盒子的距离 = 鼠标在整个页面的位置 - 所在盒子在整个页面的位置

.box {
        width: 300px;
        height: 200px;
        padding-top: 100px;
        background-color: pink;
        margin: 100px;
        text-align: center;
        font: 18px/30px "simsun";
        cursor: pointer;
    }
// <div class="box"></div>

//需求:鼠标进入盒子之后只要移动,哪怕1像素,随时显示鼠标在盒子中的坐标。
//技术点:新事件,onmousemove:在事件源上,哪怕鼠标移动1像素也会触动这个事件。
//一定程度上,模拟了定时器
//步骤:
//1.老三步和新五步
//2.获取鼠标在整个页面的位置
//3.获取盒子在整个页面的位置
//4.用鼠标的位置减去盒子的位置赋值给盒子的内容。

//1.老三步和新五步
var div = document.getElementsByTagName("div")[0];

div.onmousemove = function (event) {

    event = event || window.event;
    //2.获取鼠标在整个页面的位置
    var pagex = event.pageX || scroll().left + event.clientX;
    var pagey = event.pageY || scroll().top + event.clientY;
    //3.获取盒子在整个页面的位置
    // var xx =
    // var yy =
    //4.用鼠标的位置减去盒子的位置赋值给盒子的内容。
    var targetx = pagex - div.offsetLeft;
    var targety = pagey - div.offsetTop;
    this.innerHTML = "鼠标在盒子中的X坐标为:" + targetx + "px;<br>鼠标在盒子中的Y坐标为:" + targety + "px;"
}

举例:商品放大镜

posted @ 2019-07-16 14:33  胤小飞  阅读(119)  评论(0)    收藏  举报