Event
事件绑定的方法有哪几种?【重要】
- 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节点。
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来判断当前点击的节点。
事件委托的优点:
- 减少内存消耗:比如对一个大量的
li列表,如果每个列表项都绑定函数,对于内存消耗很大,效率上需要消耗很多的内存,最好是是将事件绑定到父层,然后在执行事件时再去判断目标元素 - 动态绑定事件:对于动态新增的列表项,无需再手动添加事件。
鼠标事件
点击
mousedown、mouseup、click、dblclick(双击)
触发顺序: 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就是当前事件触发到达的节点。
移动
mouseover、mouseenter、 mousemove、mouseout、mouseleave
触发顺序: mouseover -> mouseenter -> mousemove -> mouseout -> mouseleave
mouseover 和 mouseenter 在鼠标进入时只触发一次,区别:当进入子节点时,会再次触发mouseover;
mouseout 和 mouseleave 在鼠标离开时只触发一次,区别:当离开子节点时时,会再次触发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,textarea的input标签会触发。change:value发生改变,change事件可以监听所有表单元素的改变(失去焦点之后)select: 监听input,type值为selectinvalid: 表单输入不合法时触发
input和change: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
dragstart 和 dragenter只会触发一次。
dragleave和dragend也只会触发一次。
<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