1 事件监听器
addEventListener(type, listener[, options|useCapture])
- type 表示时间名称 用字符串表示,并且不需要在前面加on
- listener 表示要执行的函数
- 最后一个参数默认为false ,true 为捕获执行
事件监听与事件绑定的区别
事件绑定类似于样式覆盖,只会绑定最后的事件
而同一个元素的事件监听按照从上到下按照顺序执行,有多少执行多少
事件流
事件冒泡
父级子级都绑定了监听器后,点击子级,从内到外依次执行,父级监听器也会执行
阻止事件冒泡
e.stopPropagation();
把这句话加载事件中,就只会执行该元素的监听事件了
事件捕获
第三个参数为true时控制事件捕获,点击子级,而父级的事件将会提前执行,改变了优先级
事件监听相关配置
-
capture 是否在捕获阶段执行,首先执行捕获的事件(父级)
var fa = document.querySelector('.father'); var son = document.querySelector('.son'); fa.addEventListener('click', function (e) { console.log('1'); }, { capture: true, }) son.addEventListener('click', function (e) { console.log('2'); })
-
once 是否只执行一次,为true执行一次后销毁该事件
-
passive: true 阻止清除默认事件的行为,比如之后的开发想要取消默认事件的话,就无法取消了
取消监听事件 removeEventListener
需要取消的是被绑定元素上的监听事件执行的有名函数(匿名函数无法被取消事件)
fn = function() {
console.log('father')
}
fa.addEventListener('click', fn);
fa.removeEventListener('click', fn);
2 Event事件对象
event将会以参数形式传递给事件函数
事件源
- event.target 获取触发事件的元素 可以理解为点击的是谁就是谁
- event.currentTarget 指的是当前处理该事件的元素、文档或窗口
点击子级
事件委托(事件代理)
例如留言板的删除功能,当无留言的时候,就无法获取到留言板中的删除按钮
这个时候给留言板的父级加上事件监听,只要点击到了删除的事件源,就执行删除功能
list.addEventListener('click', function (e) {
// console.log(1)
console.log(e.target.tagName)
// 事件委托/事件代理
if (e.target.tagName == 'A') {
var li = e.target.parentNode;
// console.log(li);
li.remove();
}
})
})();
- 事件委托的优点
- 可减少需要添加事件绑定的元素
- 可给新增DOM元素添加事件(在不刷新页面的情况下)
- 事件委托的缺点
- 事件处理函数中需要判断事件源增加逻辑复杂度
- 祖父级和事件源之间不能有阻止冒泡
3 常用事件
3.1 清除元素默认事件e.preventDefault();
例如鼠标右键菜单
还有a标签的跳转
3.2 鼠标移入移出事件
这俩个事件 不会在鼠标移动父子级切换过程中触发
- mousenter
- mouseleave 事件
区别
- mouseover mouseout 作用于同一平面
简单理解就是只要离开了父级平面触点(子级没有覆盖到父级的区域),父级事件就消失了
- mousenter mouseleave 作用于同一空间
通俗来讲就是只要鼠标还在父级范围内(包括子级,子级也属于父级),就会一直保持事件
鼠标的位置坐标
- Event.clientX、Event.clientY 相对于window窗口的坐标
- Event.pageX、Event.pageY 相对于整个元素面的坐标
案例:鼠标拖拽的实现
思路:
- 按下时记录此时鼠标位置坐标
- 拖动时记录拖动的距离
案例2:放大镜效果
要点:
- mask移动的距离 / mask最大能够移动的距离 = 大图片移动的距离 / 大图片最大能够移动的距离
- 图片应该放大的大小 mask的半径 / 显示图片的盒子的半径
3.3 鼠标右键菜单事件 contextmenu
<script>
// contextmenu 鼠标右键事件
window.addEventListener('contextmenu',function(e){
console.log('鼠标右键')
// 清除浏览器默认行为
e.preventDefault();
})
</script>
3.4 键盘事件
- keydown、keyup 键盘按下,键盘弹起的事件
- Event.keyCode、Event.key 键盘对应的ASCII码,键盘对应字符
- keypress:按下某个键盘键并释放时触发。如果按住某个键,会不断触发该事件。该事件处理函数返回 false 时,会取消默认的动作(如输入的键盘字符)。
- Event.altKey、Event.ctrlKey、shiftKey 按下对应键位返回true,否则false
- 制作组合键
window.addEventListener('keydown', function(e) { console.log(e.key, e.keyCode); if (e.ctrlKey) { if (e.key == 'c') console.log('复制了'); if (e.key == 'v') console.log('粘贴了'); } })
案例:键盘控制div移动
要考虑的问题:
键盘长按其实是多次执行按下事件,但是有延迟,所以方法不可行
- 解决办法,只执行一次按下键位,然后执行定时器,抬起键位后清除定时器
- 难点,如何使同一个键位只按下一次?
利用boolean数组判断某个键位是否已经按下(设置为true),然后抬起的时候再使对应键位为false(未按下)
var box = document.querySelector('.box');
var fa = document.querySelector('.fa');
var x = 0;
var y = 0;
var keys = []; // 用于存储是否按下键位false/true
window.addEventListener('keydown', function (e) {
// 相同的事件只需要按下一次
if (keys[parseInt(e.keyCode)]) return;
console.log(x, y, parseInt(e.keyCode))
keys[parseInt(e.keyCode)] = true;
var timer = setInterval(move, 40)
function move() {
switch (e.keyCode) {
case 37:
x -= 10;
break;
case 38:
y -= 10;
break;
case 39:
x += 10;
break;
case 40:
y += 10;
break;
}
// 判断边界
if (x < 0) x = 0;
if (x > fa.clientWidth - box.offsetWidth) {
x = fa.clientWidth - box.offsetWidth;
}
if (y < 0) y = 0;
if (y > fa.clientHeight - box.offsetHeight) {
y = fa.clientHeight - box.offsetHeight;
}
// 改变
box.style.left = x + 'px';
box.style.top = y + 'px';
}
window.addEventListener('keyup', function (e) {
// 释放后再使键盘按下次数变为false,方便下一次按下事件
keys[e.keyCode] = false;
clearInterval(timer);
})
})
3.5 鼠标滚轮事件
- mousewheel 用于谷歌浏览器
e.wheelDelta 事件对象里上滚和下滚的数值
- DOMMouseScroll 用于火狐浏览器
e.detail 火狐中获取上滚下滚的数值
兼容性封装
判断当前浏览器有没有对应滚动事件即可
// 传入操作的对象以及上下滚动触发的对应事件
mouseWheel(window, function(){
console.log('下')
}, function() {
console.log('上')
})
function mouseWheel(obj, downFn, upFun) {
// 火狐事件
obj.addEventListener('DOMMouseScroll', function (e) {
if (e.detail < 0) {
upFun && upFun.call(obj);
} else {
downFn && downFn();
}
})
// Chrome事件
obj.addEventListener('mousewheel', function (e) {
if (e.wheelDelta > 0) {
upFun && upFun();
} else {
downFn && downFn();
}
})
}
案例 自定义滚动条
3.6 其他事件
- dbclick 双击鼠标触发
- focus 获得焦点时触发,例如input
- blur 取消焦点时触发
- change 值改变时,一般用于单选和复选框
- submit reset 提交和重置时,这个用的少
表单其他方法
el.blur() 失去焦点
el.focus() 获得焦点
el.select() 选中
4 案例
碰撞检测
function isTouch() {
var rect1 = box.getBoundingClientRect();
var l1 = rect1.left;
var t1 = rect1.top;
var r1 = rect1.right;
var b1 = rect1.bottom;
var rect2 = box2.getBoundingClientRect();
var l2 = rect2.left;
var t2 = rect2.top;
var r2 = rect2.right;
var b2 = rect2.bottom;
return (r1 > l2 && b1 > t2 && t1 < b2 && l1 < r2);
}
框选
这是一个碰撞检测 + 拖拽选框的案例
难点
- 选框的方向
- 碰撞检测多个目标
window.addEventListener('mousemove', move);
function move(e) {
// width height 不能有负值
var x = Math.abs(e.clientX - downX);
var y = Math.abs(e.clientY - downY);
// 鼠标点击后,如果移动的方向为正方向,那么就拿downXY表示框选的位置
// 但是如果是负方向,那么框选的位置就是个不定数,需要跟着鼠标移动
var left = Math.min(downX, e.clientX);
var top = Math.min(downY, e.clientY);
box.style.left = left + 'px';
box.style.top = top + 'px';
box.style.width = x + 'px';
box.style.height = y + 'px';
boxs.forEach(function (item, index) {
if (isTouch(box, item)) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
})
}
window.addEventListener('mouseup', function () {
window.removeEventListener('mousemove', move);
box.remove();
var selects = document.querySelectorAll('.active');
Array.from(selects);
selects.forEach(function (item) {
box2.appendChild(item);
item.classList.remove('active');
})
}, {
once: true,
})
圆形碰撞检测
难点
- 两点间距离公式,勾股定理