Eloquent JavaScript #12# Handling Events

时间:2022-11-18 12:39:07

Notes

1、onclick

<button onclick="copyText()">Copy Text</button>

2、removeEventListener

<button>Act-once button</button>
<script>
let button = document.querySelector("button");
function once() {
console.log("Done.");
button.removeEventListener("click", once);
}
button.addEventListener("click", once);
</script>

3、Event objects

<button>Click me any way you want</button>
<script>
let button = document.querySelector("button");
button.addEventListener("mousedown", event => {
if (event.button == 0) {
console.log("Left button");
} else if (event.button == 1) {
console.log("Middle button");
} else if (event.button == 2) {
console.log("Right button");
}
});
</script>

4、stopPropagation

        <p>A paragraph with a <button>button</button>.</p>
<script>
let para = document.querySelector("p");
let button = document.querySelector("button");
para.addEventListener("mousedown", () => {
console.log("Handler for paragraph.");
});
// 左键传播,右键不传播。
button.addEventListener("mousedown", event => {
console.log("Handler for button.");
if(event.button == 2) event.stopPropagation();
});
</script>

5、event.target

        <button>A</button>
<button>B</button>
<button>C</button>
<script>
document.body.addEventListener("click", event => {
if(event.target.nodeName == "BUTTON") {
console.log("Clicked", event.target.textContent);
}
});
</script>

6、Default actions

        <a href="https://developer.mozilla.org/">MDN</a>
<script>
let link = document.querySelector("a");
// 事件处理器在默认行为发生之前被调用
link.addEventListener("click", event => {
console.log("Nope.");
event.preventDefault(); // 例如ctrl+w是无法被prevent的
});
</script>

7、Key events

        <p>This page turns violet when you hold the V key.</p>
<script>
window.addEventListener("keydown", event => {
if(event.key == "v") {
document.body.style.background = "violet";
console.log("repeat"); // 只要按住就会不断触发,而不是仅触发一次。
}
});
window.addEventListener("keyup", event => {
if(event.key == "v") {
document.body.style.background = "";
}
});
</script>

组合按键:

        <p>Press Control-Space to continue.</p>
<script>
window.addEventListener("keydown", event => {
if(event.key == " " && event.ctrlKey) {
console.log("Continuing!");
}
});
</script>

8、Mouse motion

一个可以拖动的进度条:

        <p>Drag the bar to change its width:</p>
<div style="background: orange; width: 60px; height: 20px">
</div>
<script>
let lastX; // Tracks the last observed mouse X position
let bar = document.querySelector("div");
bar.addEventListener("mousedown", event => {
if(event.button == 0) { // 鼠标左键
lastX = event.clientX;
window.addEventListener("mousemove", moved);
event.preventDefault(); // Prevent selection
}
}); function moved(event) {
if(event.buttons == 0) {
// MouseEvent.buttons可指示任意鼠标事件中鼠标的按键情况
// 仅按下左键1 仅按下右键2 仅按下滚轮4 ,同时按下多个则取和
// 这个示例的现象在于,只有在进度条上按下左键才可以开启事件
// 之后快速换鼠标右键(或者n个键)拖动也可以。
window.removeEventListener("mousemove", moved);
} else {
let dist = event.clientX - lastX;
let newWidth = Math.max(10, bar.offsetWidth + dist); // 10是最小宽度
bar.style.width = newWidth + "px";
lastX = event.clientX;
}
}
</script>

9、Touch events

触屏和鼠标点击是不同的,但是触屏仍然默认会触发一些鼠标事件。

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
<style>
dot {
position: absolute;
display: block;
border: 2px solid red;
border-radius: 50px;
height: 100px;
width: 100px;
}
</style>
</head> <body>
<p>Touch this page</p>
<script>
function update(event) {
for(let dot; dot = document.querySelector("dot");) {
dot.remove();
}
for(let i = 0; i < event.touches.length; i++) {
let {
pageX,
pageY
} = event.touches[i];
let dot = document.createElement("dot");
dot.style.left = (pageX - 50) + "px";
dot.style.top = (pageY - 50) + "px";
document.body.appendChild(dot);
}
}
window.addEventListener("touchstart", update);
window.addEventListener("touchmove", update);
window.addEventListener("touchend", update);
</script>
</body> </html>

10、Scroll events

监控滚动条的状况:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
<style>
#progress {
border-bottom: 2px solid blue;
width: 0;
position: fixed;
top: 0;
left: 0;
}
</style>
</head> <body>
<div id="progress"></div>
<script>
// Create some content
document.body.appendChild(document.createTextNode(
"supercalifragilisticexpialidocious ".repeat(1000))); let bar = document.querySelector("#progress");
window.addEventListener("scroll", () => {
let max = document.body.scrollHeight - innerHeight;
bar.style.width = `${(pageYOffset / max) * 100}%`;
});
</script>
</body> </html>

采用preventDefault不会阻止默认事件(滚动)发生,因为滚动是在调用事件处理器之前执行的。

11、Focus events

注意Focus事件是不会传播的。

<p>Name: <input type="text" data-help="Your full name"></p>
<p>Age: <input type="text" data-help="Your age in years"></p>
<p id="help"></p> <script>
let help = document.querySelector("#help");
let fields = document.querySelectorAll("input");
for (let field of Array.from(fields)) {
field.addEventListener("focus", event => {
let text = event.target.getAttribute("data-help");
help.textContent = text;
});
field.addEventListener("blur", event => {
help.textContent = "";
});
}
</script>

12、Load event

Load事件同样是不传播的。

beforeunload - Event reference | MDN

load - Event reference | MDN

13、Events and the event loop

事件处理器在事件发生的时候就已经被“安排”了,但是也必须等到别的脚本执行完才有机会执行。在有很多或者很耗时的脚本时,页面就有可能被“冻”住,为了解决这个问题,应该把繁重、长时间的计算放在一个单独的线程里。副脚本不会和主脚本共享作用域,只有可以表达为JSON的数据才可以在两者之间传递。

js/squareworker.js↓

addEventListener("message", event => {
postMessage(event.data * event.data);
});

index.html↓

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<script>
let squareWorker = new Worker("js/squareworker.js");
squareWorker.addEventListener("message", event => {
console.log("The worker responded:", event.data);
});
squareWorker.postMessage(10);
squareWorker.postMessage(24);
</script>
</body> </html>

14、Timers

取消setTimeout回调:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<script>
let bombTimer = setTimeout(() => {
console.log("BOOM!");
}, 500); if(Math.random() < 0.5) { // 50% chance
console.log("Defused.");
clearTimeout(bombTimer);
}
</script>
</body> </html>

取消setInterval回调:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<script>
let ticks = 0;
let clock = setInterval(() => {
console.log("tick", ticks++);
if(ticks == 10) {
clearInterval(clock);
console.log("stop.");
}
}, 200);
</script>
</body> </html>

15、Debouncing

某些类型的事件有可能会被连续触发n次(例如“鼠标移动”和“滚动”事件)。处理此类事件时,必须注意不要做太费时间的操作,否则处理程序会占用很多时间,以至于与文档的交互开始变得缓慢。如果你确实需要在这样的处理程序中做一些非常重要的事情,你可以使用setTimeout来降低触发长时操作的频率。这通常被称为Debouncing(去抖动)。有几种略有不同的实现方式。

例子一,持续输入0.5秒输出一个Typed!:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<textarea>Type something here...</textarea>
<script>
let textarea = document.querySelector("textarea");
let timeout;
textarea.addEventListener("input", () => {
clearTimeout(timeout);
timeout = setTimeout(() => console.log("Typed!"), 500);
});
</script>
</body>
</html>

例子二,间隔250ms响应一次鼠标移动:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<script>
let scheduled = null;
window.addEventListener("mousemove", event => {
if(!scheduled) {
setTimeout(() => {
document.body.textContent =
`Mouse at ${scheduled.pageX}, ${scheduled.pageY}`;
scheduled = null;
}, 250);
}
scheduled = event;
});
</script>
</body> </html>

Exercise

① Balloon

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<p>