【JavaScript】事件Event

184 阅读5分钟

Event

事件绑定的方法有哪几种?【重要】

  1. HTML的on-属性
<div onclick='dosomething()'></div>

tips: 这里doSomething的内部的this指向全局对象。

2.dom.onclick 事件

let dom = document.getElementById('id');
dom.onclick =function(event){
    console.log('触发事件')
}

tips: 这里doSomething的内部的this指向触发该事件的dom节点。

  1. dom.addEventListener()同一个事件可以添加多个监听函数, 可以执行【捕获/冒泡】触发监听函数。
dom.addEventListener('load',domSomething,false);//false表示冒泡 true表示捕获

tips: 这里doSomething的内部的this指向触发该事件的dom节点。

事件触发阶段【重要】

顺序 : 捕获 -> target -> 冒泡

<div class="first">
    <div class="second">
      <div class="third">1</div>
    </div>
  </div>
      <script>
        let first = document.getElementsByClassName('first')[0];
        let second = document.getElementsByClassName('second')[0];
        let third = document.getElementsByClassName('third')[0];
        first.addEventListener('click',function(e){
          // currentTarget表示当前事件的激活对象节点, 而e.target是点击触发的对象节点
          console.log('first捕获')
        },true)
        first.addEventListener('click',function(e){
          console.log('first冒泡')
        },false)
        second.addEventListener('click',function(e){
          console.log('second冒泡')
        },false);
        second.addEventListener('click',function(e){
          console.log('second捕获')
        },true)
        third.addEventListener('click',function(e){
          console.log('third冒泡')
        },false);
        third.addEventListener('click',function(e){
          console.log('third捕获')
        },true)
      </script>

结果:

first捕获 
second捕获
third捕获
third冒泡
second冒泡
first冒泡

事件属性

e.eventPhase:

  • 0,事件目前没有发生
  • 1,事件目前处于捕获阶段
  • 2,事件到达目标阶段
  • 3,事件处于冒泡阶段

e.type: 事件类型,比如click,change,mousemove等。

e.timeStamp: 表示当前事件触发时间点,相对网页加载时间开始,返回秒级时间戳。

e.isTrusted: 是否为用户行为。

e.preventDefault(): 取消事件对当前元素的默认影响,不会阻止事件的传播。【重要】

e.stopPropagation(): 阻止捕获/冒泡事件传播。【重要】

e.stopImmediatePropagation() : 阻止捕获/冒泡事件传播。

e.composePath() : 返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。

e.stopPropagation()e.stopImmediatePropagation()区别:

e.stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比e.stopPropagation()更彻底。

let first = document.getElementsByClassName('first')[0];
        let second = document.getElementsByClassName('second')[0];
        let third = document.getElementsByClassName('third')[0];
        first.addEventListener('click',function(e){
          console.log('first捕获')
        },true)
        first.addEventListener('click',function(e){
          console.log('first冒泡')
        },false)
        second.addEventListener('click',function(e){
          // e.stopPropagation() first冒泡的回调不再执行 会触发second冒泡2 ,并不彻底
          // e.stopImmediatePropagation() 不会触发  second冒泡2
          console.log('second冒泡')
        },false);
        second.addEventListener('click',function(e){
          console.log('second冒泡2',e.currentTarget,e.target)
        },false);
        second.addEventListener('click',function(e){
          // e.stopPropagation()阻那止捕获 冒泡全部不执行,third捕获 也不执行
          console.log('second捕获',e.currentTarget,e.target)
        },true)
        third.addEventListener('click',function(e){
          console.log('third冒泡',e.currentTarget,e.target)
        },false);
        third.addEventListener('click',function(e){
          console.log('third捕获',e.currentTarget,e.target)
        },true)

second冒泡second冒泡2属于监听的同一个节点的不同函数,second冒泡监听函数使用e.stopPropagation()阻止事件传播后依旧会触发second冒泡2 , 但是使用e.stopImmediatePropagation()阻止事件传播后就不再会触发second冒泡2, 更加彻底。

事件代理(事件委托)【重要】

利用事件冒泡把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
  </ul>
  <script>
    let ul = document.getElementsByTagName('ul')[0];
    ul.addEventListener('click',function(e){
      console.log('触发',e.target)
    })
  </script>

可以使用e.target来判断当前点击的节点。

事件委托的优点:

  1. 减少内存消耗:比如对一个大量的li列表,如果每个列表项都绑定函数,对于内存消耗很大,效率上需要消耗很多的内存,最好是是将事件绑定到父层,然后在执行事件时再去判断目标元素
  2. 动态绑定事件:对于动态新增的列表项,无需再手动添加事件。

鼠标事件

点击

mousedownmouseupclickdblclick(双击)

触发顺序: mousedown -> mouseup ->click -> dblclick

<div class="test">test</div>
  <script>
    let t = document.querySelector('.test');
    // mousedown -> mouseup ->click -> dblclick
    t.addEventListener('click',function(e){
      console.log('click')
    })
    t.addEventListener('mousedown',function(e){
      console.log('mousedown')
    })
    t.addEventListener('mouseup',function(e){
      console.log('mouseup')
    })
    t.addEventListener('dblclick',function(e){
      console.log('dblclick')
    })
    
  </script>

结果:

mousedown 
mouseup 
click 
dblclick

e.target和e.currentTarget区别 e.currentTarget表示当前事件的激活对象节点, 而e.target是点击触发的对象节点

<div class="first">
    <div class="second">
      <div class="third">1</div>
    </div>
  </div>
      <script>
        let first = document.getElementsByClassName('first')[0];
        let second = document.getElementsByClassName('second')[0];
        let third = document.getElementsByClassName('third')[0];
        first.addEventListener('click',function(e){
          // currentTarget表示当前事件的激活对象节点, 而e.target是点击触发的对象节点
          console.log('first捕获',e.currentTarget,e.target)
        },true)
        first.addEventListener('click',function(e){
          console.log('first冒泡',e.currentTarget,e.target)
        },false)
        second.addEventListener('click',function(e){
          console.log('second冒泡',e.currentTarget,e.target)
        },false);
        second.addEventListener('click',function(e){
          console.log('second冒泡2',e.currentTarget,e.target)
        },false);
        second.addEventListener('click',function(e){
          console.log('second捕获',e.currentTarget,e.target)
        },true)
        third.addEventListener('click',function(e){
          console.log('third冒泡',e.currentTarget,e.target)
        },false);
        third.addEventListener('click',function(e){
          console.log('third捕获',e.currentTarget,e.target)
        },true)
      </script>
first捕获 <div class="first">​…​</div><div class="third">​1​</div>​
second捕获 <div class="second">​…​</div><div class="third">​1​</div>​
third捕获 <div class="third">​1​</div><div class="third">​1​</div>​
third冒泡 <div class="third">​1​</div><div class="third">​1​</div>​
second冒泡 <div class="second">​…​</div><div class="third">​1​</div>​
second冒泡2 <div class="second">​…​</div><div class="third">​1​</div>​
first冒泡 <div class="first">​…​</div><div class="third">​1​</div>

所以e.target一直都是点击的节点,而e.currentTarget就是当前事件触发到达的节点。

移动

mouseovermouseentermousemovemouseoutmouseleave

触发顺序: mouseover -> mouseenter -> mousemove -> mouseout -> mouseleave

mouseovermouseenter 在鼠标进入时只触发一次,区别:当进入子节点时,会再次触发mouseover

mouseoutmouseleave 在鼠标离开时只触发一次,区别:当离开子节点时时,会再次触发mouseout

mousemove 鼠标在节点内移动,会持续触发。

<style>
.box{
    width: 100px;
    height: 100px;
    border: 1px solid #000;
    background-color: aqua;
    position: relative;
  }
  .child{
    width: 50px;
    height: 50px;
    background-color: red;
    border: 1px solid #fff;
  }
</style>
<div class="box">
    <div class="child">23</div>
  </div>
  <script>
    // 触发顺序 mouseover -> mouseenter -> mousemove -> mouseout -> mouseleave
    let box = document.querySelector('.box')
    box.addEventListener('mousemove',function(e){
      console.log('mousemove')
    })
    box.addEventListener('mouseenter',function(e){
      console.log('mouseenter')
    })
    box.addEventListener('mouseover',function(e){
      console.log('mousemove',e.ctrlKey,e.shiftKey,e.altKey)
      console.log('mouseover')
    })
    box.addEventListener('mouseout',function(e){
      console.log('mouseout')
    })
    box.addEventListener('mouseleave',function(e){
      console.log('mouseleave')
    })
  </script>

如何实现一个鼠标拖动弹窗移动,讲讲原理?

#demo {
    width: 200px;
    height: 200px;
    background: rgb(137, 172, 97);
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
}
<div id="demo">
    噜噜噜
</div>
let moveOk = false
let x = 0,
    y = 0;
window.onmousemove = function (e) {
    e.preventDefault();
    if (moveOk) { 
        let left = e.pageX - x
        let top = e.pageY - y
        if (left < 0) left = 0
        if (top < 0) top = 0
        let maxLeft = window.innerWidth - demo.offsetWidth
        let maxTop = window.innerHeight - demo.offsetHeight
        if (left > maxLeft) left = maxLeft
        if (top > maxTop) top = maxTop
        demo.style.left = left + "px"
        demo.style.top = top + 'px'
    }
}
demo.onmousedown = function (e) {
    x = e.pageX - demo.offsetLeft
    y = e.pageY - demo.offsetTop
    moveOk = true
}
window.onmouseup = function () {
    moveOk = false
}
window.onblur = function () {
    moveOk = false
}

事件属性

不同事件的属性比较类似,这里列出部分属性,下面不再一一列举。

e.screenX: 数值,鼠标相对于屏幕的水平位置(px),默认0。

e.screenY : 数值,鼠标相对于屏幕的垂直位置(px),默认0。

e.clientX: 数值,鼠标相对于程序窗口的水平位置(px),默认0。

e.clientY : 数值,鼠标相对于程序窗口的垂直位置(px),默认0。

e.offsetX : 鼠标与目标节点(绑定事件节点)左侧padding边缘的水平距离(不包括margin宽度) 只读,相对原来位置。

e.offsetY : 鼠标与目标节点(绑定事件节点)上方padding边缘的垂直距离(不包括margin宽度) 只读,相对原来位置。

e.pageX : 鼠标位置与文档左侧边缘的距离(px),只读。

e.pageY : 鼠标与文档上侧边缘的距离(px),只读。

e.ctrlKey: 是否同时按下ctrl键。

e.altKey: 是否同时按下alt键。

e.shiftKey: 是否同时按下shift键。

e.button: 数值 ,表示按下哪个鼠标按键,默认值为0 左键 , 1:中键 , 2 右键

鼠标滚轮

wheel

<ul>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
    <li>1</li>
  </ul>
  <script>
    let ul = document.querySelector('ul')
    ul.addEventListener('wheel',function(e){
      console.log(e.deltaX,e.deltaY,e.deltaMode)
    })
  </script>

deltaX : 数值 ,表示滚轮的水平滚动量,默认0。

deltaY: 数值, 表示滚轮的垂直滚动量,默认0。

deltaZ: 数值, ,表示滚轮的Z轴滚动量。

deltaMode: 数值 ,表示相关的滚动事件单位, 0 :像素,1:行,2:页 。

表单事件

  • input:输入即触发,type值为radio,select,checkbox,textareainput标签会触发。
  • change: value发生改变, change事件可以监听所有表单元素的改变(失去焦点之后)
  • select: 监听inputtype值为select
  • invalid: 表单输入不合法时触发

inputchange : input事件是每次输入数字都触发一次,而change是当input失去焦点时触发。

 <form action="">
    <input  type="text">
    <select name="" id="select">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
    </select>
    <input type="select" value="1">
    <input type="text" id="invalid" required>
    <button type="submit">提交</button>
  </form>
  <script>
    let input = document.getElementsByTagName('input');
    input[0].addEventListener('input',function(e){
      console.log(e.target.value,e.data,e.inputType)//e.data表示当前输入的字串
    })
    input[0].addEventListener('change',function(e){
      console.log(e.target.value)
    })
    let select = document.querySelector('#select');
    select.addEventListener('input',function(e){
      console.log(e.target.value,e.inputType)
    })
    select.addEventListener('change',function(e){
      console.log('change',e.target.value)
    })
    input[1].addEventListener('select',function(e){
      console.log(e.target.value,'select')//可以监听
    })
    let invalid = document.getElementById('invalid');//没有输入该字段,不会跳转
    invalid.addEventListener('invalid',function(e){
      console.log('输入不合法!')
    })

触摸事件

touchstart: 触摸开始

touchmove: 触摸鼠标移动,持续触发

touchend: 触摸结束,比如弹窗取消触摸。

touchcancel: 取消触摸

触发顺序:touchstart -> touchmove -> touchend

<label for="progress"></label>
  <progress max="100" value="70" id="progress">70%</progress>
  <script>
    let progress = document.getElementById('progress');
    progress.addEventListener('touchstart',function(e){
      console.log('touchstart')
    })
    progress.addEventListener('touchend',function(e){
      console.log('touchend')
    })
    progress.addEventListener('touchmove',function(e){
      console.log('touchmove')
    })
    progress.addEventListener('touchcancel',function(e){
      console.log('touchcancel')
    })
    </script>

应用: 触摸事件 + progress实现手动滚动 ,需要在H5移动端测试,触摸事件才有效。

<label for="progress"></label>
  <progress max="100" value="70" id="progress">70%</progress>
  <script>
    let progress = document.getElementById('progress');
    let max  = 160;//进度条整个长度
    let startX;//进度调开始位置,这里是0
    progress.addEventListener('touchmove',function(e){
      let endX = e.changedTouches[0].clientX;
      let current = progress.value;
      let change = parseFloat((endX / max).toFixed(4)) * 100
      if(change >= 0 && change<=100){
        progress.value = change
      }
    },{passive:false})

拖拽

dragstart :用户开始拖拉时,在被拖拉的节点上触发。

dragenter: 拖拉进入当前节点时,在当前节点上触发一次。

drag:拖拉过程中,在被拖拉的节点上持续触发。

dragover:拖拉到当前节点上方时,在当前节点上持续触发。

dragleave: 拖拉操作离开当前节点范围时,在当前节点上触发。

dragend:拖拉结束时(释放鼠标键或按下 ESC 键)在被拖拉的节点上触发。

drop: 被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。

触发顺序:dragstart -> drag -> dragenter -> dragover -> dragleave ->drag -> dragend

dragstartdragenter只会触发一次。 dragleavedragend也只会触发一次。

<div class="box" draggable="true"></div>
  <script>
    let box = document.querySelector('.box')
    box.addEventListener('drag',function(e){
      console.log('drag')
    });
    box.addEventListener('dragstart',function(e){
      console.log('dragstart')
    });
    box.addEventListener('dragend',function(e){
      console.log('dragend')
    });
    box.addEventListener('dragenter',function(e){
      console.log('dragenter')
    });
    box.addEventListener('dragover',function(e){
      console.log('dragover')
    });
    box.addEventListener('dragleave',function(e){
      console.log('dragleave')
    });
    box.addEventListener('drop',function(e){
      console.log('drop')
    });
  </script>

结果:

dragstart
drag
dragenter
dragover//不断触发dragover和drag
drag
dragover
...
dragleave//离开当前节点
drag//不会触发drapover
drag
...
dragend//拖拽事件结束

[1] 阮一峰 JavaScript教程

[2] MDN