mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-23 20:02:20 +00:00
15.
This commit is contained in:
parent
ad87c4073a
commit
2961429306
193
15.md
193
15.md
@ -4,39 +4,39 @@
|
||||
>
|
||||
> Marcus Aurelius, Meditations
|
||||
|
||||
有些程序需要处理用户的直接输入,比如鼠标和键盘输入。这些输入的时间和顺序无法提前预知。处理这些输入需要的控制流方法与我们迄今为止所使用的方法不同。
|
||||
有些程序需要处理用户的直接输入,比如鼠标和键盘动作。这种输入方式不是组织整齐的数据结构 - 它是一次一个地,实时地出现的,并且期望程序在发生时作出响应。
|
||||
|
||||
### 14.1 事件处理器
|
||||
|
||||
想象一下,有一个接口,若想知道键盘上是否有一个键是否被按下,唯一的方法是读取那个按键的当前状态。为了能够响应按键动作,你需要不断读取键盘状态,以在按键被释放之前捕捉到按下状态。这种方法在执行时间密集计算时非常危险,因为你可能错过按键事件。
|
||||
|
||||
这是原始机器上处理输入时的方法。有一种更进一步的方法,硬件或操作系统发现按键时间并将其放入队列中。程序可以周期性地检查队列,等待新事件并在发现事件时进行响应。
|
||||
一些原始机器可以像那样处理输入。有一种更进一步的方法,硬件或操作系统发现按键时间并将其放入队列中。程序可以周期性地检查队列,等待新事件并在发现事件时进行响应。
|
||||
|
||||
当然,程序必须记得监视队列,并经常做这种事,因为任何时候,按键被按下和程序发现事件之间都会使得软件反应迟钝。该方法被称为轮询。大多数程序员都会尽可能避免这种方法。
|
||||
当然,程序必须记得监视队列,并经常做这种事,因为任何时候,按键被按下和程序发现事件之间都会使得软件反应迟钝。该方法被称为轮询。大多数程序员更希望避免这种方法。
|
||||
|
||||
一种更好的机制是底层系统给予我们的代码在事件发生时响应的机会。浏览器实现了这种特性,支持我们将函数注册为特定事件的处理器。
|
||||
一个更好的机制是,系统在发生事件时主动通知我们的代码。浏览器实现了这种特性,支持我们将函数注册为特定事件的处理器。
|
||||
|
||||
```html
|
||||
<p>Click this document to activate the handler.</p>
|
||||
<script>
|
||||
addEventListener("click", function() {
|
||||
console.log("You clicked!");
|
||||
window.addEventListener("click", () => {
|
||||
console.log("You knocked?");
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
addEventListener函数会注册第二个参数,当第一个参数描述的时间触发时,就会调用第二个参数。
|
||||
`window`绑定指向浏览器提供的内置对象。 它代表包含文档的浏览器窗口。 调用它的`addEventListener`方法注册第二个参数,以便在第一个参数描述的事件发生时调用它。
|
||||
|
||||
### 14.2 事件与DOM节点
|
||||
|
||||
每个浏览器事件处理器被注册在上下文中。当你如上所示调用addEventListener时,你可以在整个窗口上调用该方法,因为在浏览器中全局作用域等同于window对象。每个DOM元素都有其自己的addEventListener方法,支持在特定元素上监听事件。
|
||||
每个浏览器事件处理器被注册在上下文中。在为整个窗口注册处理器之前,我们在`window`对象上调用了`addEventListener`。 这种方法也可以在 DOM 元素和一些其他类型的对象上找到。 仅当事件发生在其注册对象的上下文中时,才调用事件监听器。
|
||||
|
||||
```html
|
||||
<button>Click me</button>
|
||||
<p>No handler here.</p>
|
||||
<script>
|
||||
var button = document.querySelector("button");
|
||||
button.addEventListener("click", function() {
|
||||
let button = document.querySelector("button");
|
||||
button.addEventListener("click", () => {
|
||||
console.log("Button clicked.");
|
||||
});
|
||||
</script>
|
||||
@ -44,14 +44,16 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
|
||||
示例代码中将处理器附加到按钮节点上。因此,点击按钮时会触发并执行处理器,而点击文档的其他部分则没有反应。
|
||||
|
||||
为节点执行onclick属性也有类似效果。但一个节点只有一个onclick属性,因此只能使用这种方法为每个节点注册一个处理器。而addEventListener方法则允许你添加任意数量的方法,因此你不会无意间替换掉已经注册的处理器。
|
||||
向节点提供onclick属性也有类似效果。这适用于大多数类型的事件 - 您可以为属性附加处理器,属性名称为前面带有`on`的事件名称。
|
||||
|
||||
使用参数调用removeEventListener方法与addEventListener方式相似,可以用于移除处理器。
|
||||
但是一个节点只能有一个`onclick`属性,所以你只能用这种方式为每个节点注册一个处理器。 `addEventListener`方法允许您添加任意数量的处理器,因此即使元素上已经存在另一个处理器,添加处理器也是安全的。
|
||||
|
||||
`removeEventListener`方法将删除一个处理器,使用类似于`addEventListener`的参数调用。
|
||||
|
||||
```html
|
||||
<button>Act-once button</button>
|
||||
<script>
|
||||
var button = document.querySelector("button");
|
||||
let button = document.querySelector("button");
|
||||
function once() {
|
||||
console.log("Done.");
|
||||
button.removeEventListener("click", once);
|
||||
@ -60,23 +62,24 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
</script>
|
||||
```
|
||||
|
||||
我们需要给事件处理函数一个名称(比如once),才能反注册处理器函数,并将处理函数名称传递给addEventListener和removeEventListener。
|
||||
赋予`removeEventListener`的函数必须是赋予`addEventListener`的完全相同的函数值。 因此,要注销一个处理其,您需要为该函数提供一个名称(在本例中为`once`),以便能够将相同的函数值传递给这两个方法。
|
||||
|
||||
### 14.3 事件对象
|
||||
|
||||
虽然在前面示例中被我们忽略了,但我们要知道事件触发时会为事件处理函数传递一个参数:事件(Event)对象。例如,如果我们想知道哪个鼠标按键被按下,我们可以查看事件对象的which属性。
|
||||
虽然目前为止我们忽略了它,事件处理器函数作为对象传递:事件(Event)对象。这个对象持有事件的额外信息。例如,如果我们想知道哪个鼠标按键被按下,我们可以查看事件对象的which属性。
|
||||
|
||||
```html
|
||||
<button>Click me any way you want</button>
|
||||
<script>
|
||||
var button = document.querySelector("button");
|
||||
button.addEventListener("mousedown", function(event) {
|
||||
if (event.which == 1)
|
||||
let button = document.querySelector("button");
|
||||
button.addEventListener("mousedown", event => {
|
||||
if (event.button == 0) {
|
||||
console.log("Left button");
|
||||
else if (event.which == 2)
|
||||
} else if (event.button == 1) {
|
||||
console.log("Middle button");
|
||||
else if (event.which == 3)
|
||||
} else if (event.button == 2) {
|
||||
console.log("Right button");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@ -85,7 +88,7 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
|
||||
### 14.4 传播
|
||||
|
||||
若节点含有子节点,则在节点上注册的事件处理器也会接收到在孩子节点中发生的事件。若点击一个段落中的按钮,段落的事件处理器也会收到点击事件。
|
||||
对于大多数事件类型,在具有子节点的节点上注册的处理器,也将接收发生在子节点中的事件。若点击一个段落中的按钮,段落的事件处理器也会收到点击事件。
|
||||
|
||||
但若段落和按钮都有事件处理器,则先执行最特殊的事件处理器(按钮的事件处理器)。也就是说事件向外传播,从触发事件的节点到其父节点,最后直到文档根节点。最后,当某个特定节点上注册的所有事件处理器按其顺序全部执行完毕后,窗口对象的事件处理器才有机会响应事件。
|
||||
|
||||
@ -96,15 +99,14 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
```html
|
||||
<p>A paragraph with a <button>button</button>.</p>
|
||||
<script>
|
||||
var para = document.querySelector("p");
|
||||
var button = document.querySelector("button");
|
||||
para.addEventListener("mousedown", function() {
|
||||
let para = document.querySelector("p");
|
||||
let button = document.querySelector("button");
|
||||
para.addEventListener("mousedown", () => {
|
||||
console.log("Handler for paragraph.");
|
||||
});
|
||||
button.addEventListener("mousedown", function(event) {
|
||||
button.addEventListener("mousedown", event => {
|
||||
console.log("Handler for button.");
|
||||
if (event.which == 3)
|
||||
event.stopPropagation();
|
||||
if (event.button == 2) event.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@ -118,9 +120,10 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
<button>B</button>
|
||||
<button>C</button>
|
||||
<script>
|
||||
document.body.addEventListener("click", function(event) {
|
||||
if (event.target.nodeName == "BUTTON")
|
||||
document.body.addEventListener("click", event => {
|
||||
if (event.target.nodeName == "BUTTON") {
|
||||
console.log("Clicked", event.target.textContent);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@ -129,24 +132,24 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
|
||||
大多数事件都有与其关联的默认动作。若点击链接,就会跳转到链接目标。若点击向下的箭头,浏览器会向下翻页。若右击鼠标,可以得到一个上下文菜单等。
|
||||
|
||||
对于大多数类型的事件,JavaScript事件处理器会在默认处理器执行前被调用。若事件处理器不希望执行默认行为(通常是因为已经处理了该事件),会调用preventDefault事件对象的方法。
|
||||
对于大多数类型的事件,JavaScript事件处理器会在默认行为发生之前调用。若事件处理器不希望执行默认行为(通常是因为已经处理了该事件),会调用preventDefault事件对象的方法。
|
||||
|
||||
你可以实现你自己的键盘快捷键或交互式菜单。你也可以干扰用户期望的行为。例如,这里实现一个无法跳转的链接。
|
||||
|
||||
```html
|
||||
<a href="https://developer.mozilla.org/">MDN</a>
|
||||
<script>
|
||||
var link = document.querySelector("a");
|
||||
link.addEventListener("click", function(event) {
|
||||
let link = document.querySelector("a");
|
||||
link.addEventListener("click", event => {
|
||||
console.log("Nope.");
|
||||
event.preventDefault();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
除非你有非常充足的理由,否则不要这样做。由于人们都会使用你的网页,当其期望的行为被破坏时会非常不开心。
|
||||
除非你有非常充足的理由,否则不要这样做。当预期的行为被打破时,使用你的页面的人会感到不快。
|
||||
|
||||
在有些浏览器中,你无法拦截某些事件。比如在Chrome中,关闭键盘快捷键(CTRL-W或COMMAND-W)无法由JavaScript处理。
|
||||
在有些浏览器中,你完全无法拦截某些事件。比如在Chrome中,关闭键盘快捷键(CTRL-W或COMMAND-W)无法由JavaScript处理。
|
||||
|
||||
### 14.6 按键事件
|
||||
|
||||
@ -155,68 +158,57 @@ addEventListener函数会注册第二个参数,当第一个参数描述的时
|
||||
```html
|
||||
<p>This page turns violet when you hold the V key.</p>
|
||||
<script>
|
||||
addEventListener("keydown", function(event) {
|
||||
if (event.keyCode == 86)
|
||||
window.addEventListener("keydown", event => {
|
||||
if (event.key == "v") {
|
||||
document.body.style.background = "violet";
|
||||
}
|
||||
});
|
||||
addEventListener("keyup", function(event) {
|
||||
if (event.keyCode == 86)
|
||||
window.addEventListener("keyup", event => {
|
||||
if (event.key == "v") {
|
||||
document.body.style.background = "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
尽管从keydown这个事件名上看应该是物理按键按下时触发,但当持续按下某个按键时,会循环触发该事件。有时,例如你想在按键按下时增加游戏属性,而在按键释放时减缓游戏属性,此时你要注意,不要在每次触发按键事件时都增加游戏属性,否则最后无意之间会得到巨大的值。
|
||||
尽管从keydown这个事件名上看应该是物理按键按下时触发,但当持续按下某个按键时,会循环触发该事件。有时,你想谨慎对待它。例如,如果您在按下某个按键时向 DOM 添加按钮,并且在释放按键时再次将其删除,则可能会在按住某个按键的时间过长时,意外添加数百个按钮。
|
||||
|
||||
前面的示例查看了事件对象的keyCode属性。该属性标识了用户按下或释放了哪个按键。遗憾的是将数值按键代码转换成实际按键的方法并不总是显而易见的。
|
||||
|
||||
对于字母和数字键,其按键代码是与键盘上的字母(大写)或数字相关的Unicode字符。而字符串的charCodeAt方法则让我们可以找到字符对应的代码。
|
||||
|
||||
```html
|
||||
console.log("Violet".charCodeAt(0));
|
||||
// → 86
|
||||
console.log("1".charCodeAt(0));
|
||||
// → 49
|
||||
```
|
||||
|
||||
其他的按键就不是那么可预见的了。寻找代码常常需要通过实验,注册一个按键事件处理器,记录下获得的按键代码然后按下你想了解的按键。
|
||||
该示例查看了事件对象的`key`属性,来查看事件关于哪个键。 该属性包含一个字符串,对于大多数键,它对应于按下该键时将键入的内容。 对于像`Enter`这样的特殊键,它包含一个用于命名键的字符串(在本例中为`"Enter"`)。 如果你按住一个键的同时按住`Shift`键,这也可能影响键的名称 - `"v"`变为`"V"`,`"1"`可能变成`"!"`,这是按下`Shift-1`键 在键盘上产生的东西。
|
||||
|
||||
诸如shift、ctrl、alt和meta(Mac上的command)之类的修饰按键会像普通按键一样产生事件。但在查找组合键时,你也可以查看键盘和鼠标事件的shiftKey、ctrlKey、altKey和metaKey属性来判断这些键是否被按下。
|
||||
|
||||
```html
|
||||
<p>Press Ctrl-Space to continue.</p>
|
||||
<script>
|
||||
addEventListener("keydown", function(event) {
|
||||
if (event.keyCode == 32 && event.ctrlKey)
|
||||
window.addEventListener("keydown", event => {
|
||||
if (event.key == " " && event.ctrlKey) {
|
||||
console.log("Continuing!");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
keydown和keyup事件告知我们按下的物理按键信息。但假如你想得到的是实际按下的文本该怎么办呢?从按键代码获取文本是很烦琐的。有另一个事件可以取而代之,该事件名为keypress,该事件在keydown之后触发(当按键按住不放时与keydown一样会反复触发),但只获得按键的输入。
|
||||
按键事件发生的 DOM 节点取决于按下按键时具有焦点的元素。 大多数节点不能拥有焦点,除非你给他们一个`tabindex`属性,但像链接,按钮和表单字段可以。 我们将在第 18 章中回顾表单字段。 当没有特别的焦点时,`document.body`充当按键事件的目标节点。
|
||||
|
||||
```html
|
||||
<p>Focus this page and type something.</p>
|
||||
<script>
|
||||
addEventListener("keypress", function(event) {
|
||||
console.log(String.fromCharCode(event.charCode));
|
||||
});
|
||||
</script>
|
||||
```
|
||||
当用户键入文本时,使用按键事件来确定正在键入的内容是有问题的。 某些平台,尤其是 Android 手机上的虚拟键盘,不会触发按键事件。 但即使你有一个老式键盘,某些类型的文本输入也不能直接匹配按键,例如其脚本不适合键盘的人所使用的 IME(“输入法编辑器”)软件 ,其中组合多个热键来创建字符。
|
||||
|
||||
按键事件的起源DOM节点取决于按键按下时,焦点在哪个元素上。正常的节点无法拥有焦点(除非你赋予其tabindex属性),但诸如链接、按钮和表单字段之类的元素都可以拥有焦点。我们将在第18章中回过头来看表单字段。当任何元素都没有焦点时,document.body扮演了按键事件的目标节点。
|
||||
要注意什么时候输入了内容,每当用户更改其内容时,可以键入的元素(例如`<input>`和`<textarea>`标签)触发`"input"`事件。为了获得输入的实际内容,最好直接从焦点字段中读取它。 第 18 章将展示如何实现。
|
||||
|
||||
### 14.7 鼠标点击
|
||||
## 指针事件
|
||||
|
||||
点击鼠标按钮也会触发一系列事件。“mousedown”事件和“mouseup”事件类似于“keydown”和“keyup”事件,当鼠标按钮按下或释放时触发。当事件发生时,由鼠标指针下方的DOM节点触发事件。
|
||||
目前有两种广泛使用的方式,用于指向屏幕上的东西:鼠标(包括类似鼠标的设备,如触摸板和轨迹球)和触摸屏。 它们产生不同类型的事件。
|
||||
|
||||
### 鼠标点击
|
||||
|
||||
点击鼠标按钮会触发一系列事件。“mousedown”事件和“mouseup”事件类似于“keydown”和“keyup”事件,当鼠标按钮按下或释放时触发。当事件发生时,由鼠标指针下方的DOM节点触发事件。
|
||||
|
||||
在mouseup事件后,包含鼠标按下与释放的特定节点会触发“click”事件。例如,如果我在一个段落上按下鼠标,移动到另一个段落上释放鼠标,“click”事件会发生在包含这两个段落的元素上。
|
||||
|
||||
若两次点击事件触发时机接近,则在第二次点击事件之后,也会触发“dbclick”(双击,double-click)事件。
|
||||
|
||||
为了获得鼠标事件触发的精确信息,你可以查看事件中的pageX和pageY属性,包含了事件相对于文档左上角的坐标(以像素为单位)。
|
||||
为了获得鼠标事件触发的精确信息,你可以查看事件中的`clientX`和`clientY`属性,包含了事件相对于窗口左上角的坐标(以像素为单位)。或`pageX`和`pageY`,它们相对于整个文档的左上角(当窗口被滚动时可能不同)。
|
||||
|
||||
下面的代码实现了简单的绘图程序。每次点击文档时,会在鼠标指针下添加一个点。还有一个稍微优化的绘图程序,请参见第19章。
|
||||
下面的代码实现了简单的绘图程序。每次点击文档时,会在鼠标指针下添加一个点。还有一个稍微优化的绘图程序,请参见第 19 章。
|
||||
|
||||
```html
|
||||
<style>
|
||||
@ -232,8 +224,8 @@ keydown和keyup事件告知我们按下的物理按键信息。但假如你想
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
addEventListener("click", function(event) {
|
||||
var dot = document.createElement("div");
|
||||
window.addEventListener("click", event => {
|
||||
let dot = document.createElement("div");
|
||||
dot.className = "dot";
|
||||
dot.style.left = (event.pageX - 4) + "px";
|
||||
dot.style.top = (event.pageY - 4) + "px";
|
||||
@ -242,9 +234,7 @@ keydown和keyup事件告知我们按下的物理按键信息。但假如你想
|
||||
</script>
|
||||
```
|
||||
|
||||
clientX和clientY属性类似于pageX和pageY属性,是相对于文档当前视图中左上角的坐标。当与getBoundingClientRect返回的坐标比较时(返回的也是相对于视口的坐标),该信息非常有用。
|
||||
|
||||
### 14.8 鼠标移动
|
||||
### 鼠标移动
|
||||
|
||||
每次鼠标移动时都会触发“mousemove”事件。该事件可用于跟踪鼠标位置。当实现某些形式的鼠标拖拽功能时,该事件非常有用。
|
||||
|
||||
@ -255,62 +245,41 @@ clientX和clientY属性类似于pageX和pageY属性,是相对于文档当前
|
||||
<div style="background: orange; width: 60px; height: 20px">
|
||||
</div>
|
||||
<script>
|
||||
var lastX; // Tracks the last observed mouse X position
|
||||
var rect = document.querySelector("div");
|
||||
rect.addEventListener("mousedown", function(event) {
|
||||
if (event.which == 1) {
|
||||
lastX = event.pageX;
|
||||
addEventListener("mousemove", moved);
|
||||
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 buttonPressed(event) {
|
||||
if (event.buttons == null)
|
||||
return event.which != 0;
|
||||
else
|
||||
return event.buttons != 0;
|
||||
}
|
||||
|
||||
function moved(event) {
|
||||
if (!buttonPressed(event)) {
|
||||
removeEventListener("mousemove", moved);
|
||||
if (event.buttons == 0) {
|
||||
window.removeEventListener("mousemove", moved);
|
||||
} else {
|
||||
var dist = event.pageX - lastX;
|
||||
var newWidth = Math.max(10, rect.offsetWidth + dist);
|
||||
rect.style.width = newWidth + "px";
|
||||
lastX = event.pageX;
|
||||
let dist = event.clientX - lastX;
|
||||
let newWidth = Math.max(10, bar.offsetWidth + dist);
|
||||
bar.style.width = newWidth + "px";
|
||||
lastX = event.clientX;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
请注意,mousemove处理器注册在窗口对象上。即使鼠标在改变窗口尺寸时在栏外侧移动,我们也想看到其尺寸变化,而当鼠标释放时,拖拽停止。
|
||||
请注意,mousemove处理器注册在窗口对象上。即使鼠标在改变窗口尺寸时在栏外侧移动,只要按住按钮,我们仍然想要更新其大小。
|
||||
|
||||
每当鼠标指针进入或离开节点时,都会触发mouseover和mouseout事件。这两个事件常用于创建悬浮效果,在鼠标移过特定元素时显示或加上特定样式。
|
||||
释放鼠标按键时,我们必须停止调整栏的大小。 为此,我们可以使用`buttons`属性(注意复数形式),它告诉我们当前按下的按键。 当它为零时,没有按下按键。 当按键被按住时,其值是这些按键的代码总和 - 左键代码为 1,右键为 2,中键为 4。 这样,您可以通过获取`buttons`的剩余值及其代码,来检查是否按下了给定按键。
|
||||
|
||||
遗憾的是创建特定效果并不是很简单,不仅仅是mouseover时触发效果,mouseout时效果结束。当鼠标从一个节点移动到其孩子节点上时,mouseout会触发其父亲节点,尽管鼠标没有移动到其范围之外。更糟糕的是这些事件会像其他事件一样扩散,因此当鼠标离开某个节点的子节点时,注册在该节点上的处理器也会收到mouseout事件。
|
||||
请注意,这些代码的顺序与`button`使用的顺序不同,中键位于右键之前。 如前所述,一致性并不是浏览器编程接口的强项。
|
||||
|
||||
为了解决此问题,我们可以使用创建事件对象的relatedTarget属性。在mouseover事件中,该属性指的是指针之间移动过的元素,而在mouseout中则是刚刚离开的元素。只有当relatedTarget属性在目标节点外时我们才启用浮动效果。只有这种情况,事件发生时确实是从元素外部移动到元素内部(或其他方式)。
|
||||
### 触摸事件
|
||||
|
||||
我们使用的图形浏览器的风格,是考虑到鼠标界面的情况下而设计的,那个时候触摸屏非常罕见。 为了使网络在早期的触摸屏手机上“工作”,在某种程度上,这些设备的浏览器假装触摸事件是鼠标事件。 如果你点击你的屏幕,你会得到`'mousedown'`,`'mouseup'`和`'click'`事件。
|
||||
|
||||
```html
|
||||
<p>Hover over this <strong>paragraph</strong>.</p>
|
||||
<script>
|
||||
var para = document.querySelector("p");
|
||||
function isInside(node, target) {
|
||||
for (; node != null; node = node.parentNode)
|
||||
if (node == target) return true;
|
||||
}
|
||||
para.addEventListener("mouseover", function(event) {
|
||||
if (!isInside(event.relatedTarget, para))
|
||||
para.style.color = "red";
|
||||
});
|
||||
para.addEventListener("mouseout", function(event) {
|
||||
if (!isInside(event.relatedTarget, para))
|
||||
para.style.color = "";
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
函数isInside会沿着指定节点父节点链接寻找节点,直到到达文档顶部(当node为null时),或找到了目标父节点为止。
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user