17 事件
事件流 事件处理程序 不同类型事件
JavaScript 与 HTML 的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。 可以使用仅在事件发生时执行的监听器(也叫处理程)订阅事件。
换句话说页面上发生了什么事情,对应执行什么操作
事件流
页面哪个部分拥有特定的事件呢? 这个事件归谁管啊?
事件流描述了页面接收事件的顺。
IE 和 Netspace 不一样
IE 事件冒泡 从下到上
由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡,特殊情况下可以使用事件捕获。
Netspace 事件捕获 从上到下
事件处理程序
事件意味着用户或浏览器执行的某种动作。比如,单击(click)、加载(load)、鼠标悬停 (mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)。事件处理程的名字 以"on"开头,因此 click 事件的处理程叫作 onclick,而 load 事件的处理程叫作 onload。有
html 事件处理
作为事件处理程执行的代码可以访问全局作用域中的 一切。
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="Echo Username"
onclick="console.log(username.value)">
</form>
点击这个例子中的按钮会显示出文本框中包含的文本。注意,事件处理程中的代码直接引用了 username。
捕获错误
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex) {}">
用 js 事件处理
DOM2
DOM2 Events 为事件处理程的赋值和移除定义了两个方法:addEventListener()
和 remove- EventListener()
通过 addEventListener()添加的事件处理程只能使用 removeEventListener()并传入与添 加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除
let btn = document.getElementById("myBtn");
let handler = function() {
console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 有效果!
跨浏览器事件处理
为啥?因为 IE 9 之前他妈的不兼容 现在好像兼容了
检测是否兼容 DOM2 方式
事件对象
所有浏览器都支持这个 event 对象,尽管支持方式不同。
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
console.log("Body clicked");
};
如果这个例子中不调用 stopPropagation(),那么点击按钮就会打印两条消息。但这里由于 click 事件不会传播到 document.body,因此 onclick 事件处理程永远不会执行。
事件类型
事件监听器的注册过程是同步的,但其回调函数的执行是异步的。也就是说,当你用 addEventListener
注册一个事件时,这个注册动作是立刻完成的;而当事件真正发生时,回调函数会被放入任务队列中,在当前执行栈清空后才执行,这就是异步执行的行为。
DOM3 events
-
用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
-
焦点事件(FocusEvent):在元素获得和失去焦点时触发。
-
鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
-
滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
-
输入事件(InputEvent):向文档中输入文本时触发。
-
键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
-
合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入 27
字符时触发。
UIEvent
常见的 UIEvent 类型通常包括:
- load/unload:页面或资源加载完成与卸载时触发的事件
- resize:当窗口或视图尺寸变化时触发
- scroll:页面滚动时触发
- select:文本或表单元素中的内容被选中时触发
img 加 load 事件要写在 src 前面
FocusEvent
在 FocusEvent 中,最常用的事件是:
- focus:当元素获得焦点时触发。
- blur:当元素失去焦点时触发。
此外,还有:
- focusin 和 focusout:这两个事件与 focus/blur 类似,但它们是冒泡的(即事件会从目标元素向上冒泡),在某些场景下会更适用。
MouseEvent & WheelEvent
在鼠标交互中,最常用的 MouseEvent 事件包括:
- click:单击事件,当鼠标按下并释放时触发。
- dblclick:双击事件,当快速连续点击两次时触发。
- mousedown:鼠标按下时触发。
- mouseup:鼠标释放时触发。
- mousemove:鼠标移动时持续触发。
- mouseenter / mouseleave:当鼠标进入或离开元素时触发(不冒泡)。
- mouseover / mouseout:当鼠标悬停于元素上或离开时触发(会冒泡)。 存在相关元素
虽然没有专门命名为“右键事件”,但当用户点击鼠标右键时,会触发一个叫做 contextmenu 的事件。你可以监听这个事件,并调用 preventDefault()
来禁用默认的右键菜单。
而对于 WheelEvent,最常用的就是:
-
wheel:当鼠标滚轮滚动或触控板进行滚动操作时触发。
触摸屏
-
单指点触屏幕上的可点击元素会触发 mousemove 事件。如果操作会导致内容变化,则不会再触 发其他事件。如果屏幕上没有变化,则会相继触发 mousedown、mouseup 和 click 事件。点 触不可点击的元素不会触发事件。可点击元素是指点击时有默认动作的元素(如链接)或指定
了 onclick 事件处理程的元素。
-
mousemove 事件也会触发 mouseover 和 mouseout 事件。
-
双指点触屏幕并滑动导致页面滚动时会触发 mousewheel 和 scroll 事件。
专为触摸设备的触摸事件
touchstart 等
KeyboardEvent & InputEvent
KeyboardEvent 常用事件
-
keydown
当键盘上的任意键被按下时触发。- 特点:按键按下时立即触发;如果持续按住,会重复触发该事件。
- 常用场景:捕获用户按键、快捷键监听等。
-
keyup
当键盘上的按键被释放时触发。- 特点:只触发一次,标识按键释放的时刻。
- 常用场景:检测用户输入结束、校验输入状态等。
document.addEventListener('keydown', function (e) {
// 检查是否同时按下了 Ctrl 键和 's' 键
if (e.ctrlKey && e.key.toLowerCase() === 's') {
e.preventDefault(); // 阻止默认保存网页的行为
console.log("触发保存操作!");
// 在这里添加保存数据的逻辑
}
});
InputEvent
-
textInput
当元素的值发生变化时触发,无论是通过用户输入、剪切、粘贴或其他操作。- 特点:即时响应输入变化,适用于实时验证、联想搜索等需求。
- 常用场景:动态表单验证、实时搜索提示、同步显示用户输入等。
-
compositionstart / compositionupdate / compositionend
这组事件用于处理输入法(IME)的文本组合过程。- compositionstart:开始输入复合字符时触发。
- compositionupdate:在输入过程中持续触发,反映当前组合状态。
- compositionend:完成复合字符输入时触发。
- 常用场景:多语言输入、需要对输入过程进行特殊处理的场合。
内存与性能
每个函数都是对象,都占用内存空间,对象越多,性能越差。其次,
为指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。
事件太多-事件委托
过多事件处理程”的解决方案是使用事件委托
事件委托利用事件冒泡,可以只使用一个事件 处理程来管理一种类型的事件。
这意味着可以为整个页面指定 一个 onclick 事件处理程,而不用为每个可点击元素分别指定事件处理程。
使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程, 就可以解决问题。
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
let target = event.target;
switch(target.id) {
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http:// www.wrox.com";
break;
case "sayHi":
console.log("hi");
break;
} });
只要可行,就应该考虑只给 document 添加一个事件处理程序
click、mousedown、mouseup、keydown 和 keypress。比较适合
不用的及时删除-删除事件处理程序
把事件处理程指定给元素后,在浏览器代码和负责页面交互的 JavaScript 代码之间就建立了联系。这种联系建立得越多,页面性能就越差。
及时删除不用
很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。
出现垃圾事件的情况:
-
删除带有事件处理程序的元素。
-
最常见的还是使用 innerHTML 整体替换页面的 某一部分。这时候,被 innerHTML 删除的元素上如果有事件处理程,就不会被垃圾收集程正常清 理。
-
另一个可能导致内存中残留引用的问题是页面卸载。
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
let btn = document.getElementById("myBtn");
btn.onclick = function() {
// 执行操作
document.getElementById("myDiv").innerHTML = "Processing...";
// 不好! };
</script>
烂!因为事件没删
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
let btn = document.getElementById("myBtn");
btn.onclick = function() {
// 执行操作
btn.onclick = null; // 删除事件处理程序
document.getElementById("myDiv").innerHTML = "Processing..."; };
</script>
一般来说,最好在 onunload 事件处理程中趁页面尚未卸载先删除所有事件处理程序
onload 事件处理程中做了什么,最好在 onunload 事件处理程中恢复。
模拟事件
可以通过 JavaScript 在任何时候触发任意事件,而这些事件会被当成浏览器创建 的事件
DOM 事件模拟
任何时候,都可以使用 document.createEvent()方法创建一个 event 对象。
鼠标事件和键盘事件是浏览器中最常见的模拟对象。
创建 event-传入必要参数-触发事件
let btn = document.getElementById("myBtn");
// 创建 event 对象
let event = document.createEvent("MouseEvents");
// 初始化 event 对象
event.initMouseEvent("click", true, true, document.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
// 触发事件
btn.dispatchEvent(event);
18 动画与 canvas
<canvas>
是 HTML5 最受欢迎的新特性。这个元素会占据一块页面区域,让 JavaScript 可以动态在上面绘制图片
18.1 使用 requestAnimationFrame
般计算机显示器的屏 幕刷新率都是 60Hz,基本上意味着每秒需要重绘 60 次。
因此,实现平滑动画最佳的重绘间隔为 1000 毫秒/60,大约 17 毫秒。以这个速度重绘可以实现最平 滑的动画,因为这已经是浏览器的极限了。
知道何时绘制下一帧是创造平滑动画的关键。(每秒内的每次刷新时绘制)
所有浏览器都支持这个方法不带前缀的版本,即 requestAnimationFrame()。接收一个动画操作的参数
与 setTimeout()类似,requestAnimationFrame()也返回一个请求 ID,可以用于通过另一个 方法 cancelAnimationFrame()来取消重绘任务。下面的例子展示了刚把一个任务加入队列又立即将 其取消:
let requestID = window.requestAnimationFrame(() => {
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);
所谓钩子(hook),就是浏览器在执行下一次重 绘之前的一个点。每次调用 requestAnimationFrame()都会在队列上推入一个回调函数,队列的长度没有限制。在频繁执 行影响页面外观的代码时(比如滚动事件监听器),可以利用这个回调队列进行节流。
如果想把事件处理程的调用限制在每次重绘前发生,那么可以像这样下面把它封装到 request- AnimationFrame()调用中:
let enabled = true;
function expensiveOperation() {
console.log('Invoked at', Date.now());
}
window.addEventListener('scroll', () => {
if (enabled) {
enabled = false; window.requestAnimationFrame(expensiveOperation); window.setTimeout(() => enabled = true, 50);
}
});
18.2 基本画布功能
创建<canvas>
元素时至少要设置其 width 和 height 属性
创建对象-取得绘图上下文-绘图-可以导出
getContext
toDataURL
18.3 2d 绘图上下文
填充和描边:
fillStyle 和 strokeStyle。
fillRect()、strokeRect()和 clearRect()
绘制路径:beginPath()
绘制文本:fillText() 和 strokeText()。
变换
图像,阴影,渐变
18.4 WebGL 3D
以后再说