EventTarget接口、事件模型
需要理解的部分: 1、addEventListene三个参数的含义 2、DOM的this指向 3、事件的传播 4、事件的代理
addEventListener()
target.addEventListener(type, listener[, useCapture]);
- type:事件名称,大小写敏感。
- listener:监听函数。事件发生时,会调用该监听函数。
useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。
事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
- 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
上面代码表示,click事件被触发了四次:
节点的目标阶段触发了2次。
- 捕获阶段:事件从向
传播时,触发
的click事件; - 目标阶段:事件从到达
时,触发
的click事件;
- 冒泡阶段:事件从
传回
时,再次触发的click事件。
其中,
节点有两个监听函数(addEventListener方法第三个参数的不同,会导致绑定两个监听函数),因此它们都会因为click事件触发一次。所以,
会在target阶段有两次输出。
注意,浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是
节点)。所以,
节点的捕获阶段和冒泡阶段,都会显示为target阶段。
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)。也就是说,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、div、p,在冒泡阶段依次为p、div、body、html、document、window。
事件代理
在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理
var ul = document.querySelector('ul');
ul.addEventListener('click', function (event) {
if (event.target.tagName.toLowerCase() === 'li') {
// some code
}
});
event.stopPropagation();阻止冒泡的方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#container {
width: 100px;
height: 100px;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="container">
<button id="btn" attr="111">点击</button>
</div>
<script>
var button = document.getElementById('btn');
button.addEventListener('click', function (event) {
event.stopPropagation();
alert('Hello world');
}, false);
var div = document.getElementById('container');
// 这里重点理解捕获阶段和冒泡阶段的区别
div.addEventListener('click', hellos, false);
function hellos() {
alert('Hello world');
}
</script>
</body>
</html>
Event对象
重点掌握实例方法:
- Event.preventDefault()
- Event.stopPropagation()
- Event.stopImmediatePropagation()
Event.preventDefault()
Event.preventDefault方法取消浏览器对当前事件的默认行为。该方法生效的前提是,事件对象的cancelable属性为true,如果为false,调用该方法没有任何效果。
该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用stopPropagation()或stopImmediatePropagation()方法。
// <input type="checkbox" id="my-checkbox" />
var cb = document.getElementById('my-checkbox');
cb.addEventListener(
'click',
function (e){ e.preventDefault(); },
false
);
Event.stopPropagation()
stopPropagation方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。
Event.stopImmediatePropagation()
Event.stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()更彻底。
鼠标事件
MouseEvent.clientX,MouseEvent.clientY
MouseEvent.clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标(单位像素),MouseEvent.clientY属性返回垂直坐标。这两个属性都是只读属性。
MouseEvent.movementX,MouseEvent.movementY
MouseEvent.movementX属性返回当前位置与上一个mousemove事件之间的水平距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementX = currentEvent.screenX - previousEvent.screenX
MouseEvent.movementY属性返回当前位置与上一个mousemove事件之间的垂直距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。
MouseEvent.screenX,MouseEvent.screenY
MouseEvent.screenX属性返回鼠标位置相对于屏幕左上角的水平坐标(单位像素)
MouseEvent.screenY属性返回垂直坐标。这两个属性都是只读属性。
// HTML 代码如下
// <body onmousedown="showCoords(event)">
function showCoords(evt) {
console.log(
'screenX value: ' + evt.screenX + '\n',
'screenY value: ' + evt.screenY + '\n'
);
}
MouseEvent.offsetX,MouseEvent.offsetY
MouseEvent.offsetX属性返回鼠标位置与目标节点左侧的padding边缘的水平距离(单位像素),MouseEvent.offsetY属性返回与目标节点上方的padding边缘的垂直距离。这两个属性都是只读属性。
/* HTML 代码如下
<style>
p {
width: 100px;
height: 100px;
padding: 100px;
}
</style>
<p>Hello</p>
*/
var p = document.querySelector('p');
p.addEventListener(
'click',
function (e) {
console.log(e.offsetX);
console.log(e.offsetY);
},
false
);
MouseEvent.pageX,MouseEvent.pageY
MouseEvent.pageX属性返回鼠标位置与文档左侧边缘的距离(单位像素),MouseEvent.pageY属性返回与文档上侧边缘的距离(单位像素)。它们的返回值都包括文档不可见的部分。这两个属性都是只读。
/* HTML 代码如下
<style>
body {
height: 2000px;
}
</style>
*/
document.body.addEventListener(
'click',
function (e) {
console.log(e.pageX);
console.log(e.pageY);
},
false
);
拖拉事件
1、拖拽的几个事件
实现元素块的任意拖拉拽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="move-container">
<div class="move" style="position:absolute; width:100px; height:100px; background:gray">
</div>
</div>
<script>
var moveElem = document.querySelector('.move'); //待拖拽元素
var dragging; //是否激活拖拽状态
var tLeft, tTop; //鼠标按下时相对于选中元素的位移
//监听鼠标按下事件
document.addEventListener('mousedown', function (e) {
if (e.target == moveElem) {
dragging = true; //激活拖拽状态
var moveElemRect = moveElem.getBoundingClientRect();
tLeft = e.clientX - moveElemRect.left; //鼠标按下时和选中元素的坐标偏移:x坐标
tTop = e.clientY - moveElemRect.top; //鼠标按下时和选中元素的坐标偏移:y坐标
}
});
//监听鼠标放开事件
document.addEventListener('mouseup', function (e) {
dragging = false;
});
//监听鼠标移动事件
document.addEventListener('mousemove', function (e) {
if (dragging) {
var moveX = e.clientX - tLeft,
moveY = e.clientY - tTop;
moveElem.style.left = moveX + 'px';
moveElem.style.top = moveY + 'px';
}
});
</script>
</body>
</html>
其他事件
1、资源事件 2、session 历史事件 3、窗口事件【scroll】、resize 事件 4、剪贴板事件
资源事件
beforeunload事件在窗口、文档、各种资源将要卸载前触发。它可以用来防止用户不小心卸载资源。
大多数浏览器在对话框中不显示指定文本,只显示默认文本。
window.addEventListener('beforeunload', function (e) {
var confirmationMessage = '确认关闭窗口?';
e.returnValue = confirmationMessage;
return confirmationMessage;
});
popstate 事件
popstate事件在浏览器的history对象的当前记录发生显式切换时触发。
window.onpopstate = function (event) {
console.log('state: ' + event.state);
};
history.pushState({page: 1}, 'title 1', '?page=1');
history.pushState({page: 2}, 'title 2', '?page=2');
history.replaceState({page: 3}, 'title 3', '?page=3');
history.back(); // state: {"page":1}
history.back(); // state: null
history.go(2); // state: {"page":3}
hashchange 事件
hashchange事件在 URL 的 hash 部分(即#号后面的部分,包括#号)发生变化时触发。该事件一般在window对象上监听。
// URL 是 http://www.example.com/
window.addEventListener('hashchange', myFunction);
function myFunction(e) {
console.log(e.oldURL);
console.log(e.newURL);
}
location.hash = 'part2';
// http://www.example.com/
// http://www.example.com/#part2
scroll 事件
这里需要注意解决大量触发的事件。也就是常说的节流
(function () {
var throttle = function (type, name, obj) {
var obj = obj || window;
var running = false;
var func = function () {
if (running) { return; }
running = true;
requestAnimationFrame(function() {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj.addEventListener(type, func);
};
// 将 scroll 事件转为 optimizedScroll 事件
throttle('scroll', 'optimizedScroll');
})();
window.addEventListener('optimizedScroll', function() {
console.log('Resource conscious scroll callback!');
});
resize 事件
resize事件在改变浏览器窗口大小时触发,主要发生在window对象上面。
可以动态计算iframe的大小视口
var resizeMethod = function () {
if (document.body.clientWidth < 768) {
console.log('移动设备的视口');
}
};
window.addEventListener('resize', resizeMethod, true);
剪贴板事件
需求:选中文字并把文字复制到粘贴板中
<input type="text" value="被选中的对象" id="input"/>
<script>
const inputElement = document.querySelector('#input');
inputElement.select();
inputElement.addEventListener('click',function () {
document.execCommand('copy');
})
</script>