许多事件会自动导致浏览器执行某些操作。
例如
- 点击链接 - 启动导航到其 URL。
- 点击表单提交按钮 - 启动将其提交到服务器。
- 在文本上按下鼠标按钮并移动它 - 选择文本。
如果我们在 JavaScript 中处理事件,我们可能不希望发生相应的浏览器操作,而希望实现另一种行为。
阻止浏览器操作
有两种方法可以告诉浏览器我们不希望它执行操作
- 主要方法是使用
event
对象。有一个方法event.preventDefault()
。 - 如果使用
on<event>
(而不是addEventListener
)分配处理程序,则返回false
也同样有效。
在此 HTML 中,单击链接不会导致导航;浏览器不会执行任何操作
<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>
在下一个示例中,我们将使用此技术来创建一个由 JavaScript 驱动的菜单。
通常会忽略事件处理程序返回的值。
唯一的例外是从使用 `on<event>` 分配的处理程序返回 `false`。
在所有其他情况下,都会忽略 `return` 值。特别是,返回 `true` 没有意义。
示例:菜单
考虑一个站点菜单,如下所示
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
以下是它在使用一些 CSS 时的外观
菜单项实现为 HTML 链接 `<a>`,而不是按钮 `<button>`。这样做有几个原因,例如
- 许多人喜欢使用“右键单击” - “在新窗口中打开”。如果我们使用 `<button>` 或 `<span>`,则无法使用该功能。
- 搜索引擎在索引时会遵循 `<a href="...">` 链接。
因此我们在标记中使用 `<a>`。但我们通常打算在 JavaScript 中处理单击。因此我们应该防止默认浏览器操作。
如下所示
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...can be loading from the server, UI generation etc
return false; // prevent browser action (don't go to the URL)
};
如果我们省略 `return false`,那么在我们的代码执行后,浏览器将执行其“默认操作” - 导航到 `href` 中的 URL。我们在这里不需要这样做,因为我们自己处理单击。
顺便说一句,在此处使用事件委托使我们的菜单非常灵活。我们可以添加嵌套列表,并使用 CSS 为其设置样式以“向下滑动”。
某些事件会依次发生。如果我们阻止第一个事件,则不会有第二个事件。
例如,`<input>` 字段上的 `mousedown` 会导致聚焦到该字段,并触发 `focus` 事件。如果我们阻止 `mousedown` 事件,则不会聚焦。
尝试单击下面的第一个 `<input>` - 会发生 `focus` 事件。但如果你单击第二个,则不会聚焦。
<input value="Focus works" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Click me">
这是因为浏览器操作在 `mousedown` 上被取消了。如果我们使用另一种方式输入输入,仍然可以聚焦。例如,使用 Tab 键从第一个输入切换到第二个输入。但不能再使用鼠标单击了。
“passive” 处理程序选项
`addEventListener` 的可选 `passive: true` 选项向浏览器发出信号,表明处理程序不会调用 `preventDefault()`。
为什么可能需要这样做?
移动设备上有一些事件,比如 touchmove
(当用户在屏幕上移动手指时),默认情况下会引起滚动,但可以使用处理程序中的 preventDefault()
来防止滚动。
因此,当浏览器检测到此类事件时,它首先必须处理所有处理程序,然后如果在任何地方都没有调用 preventDefault
,它就可以继续滚动。这可能会导致 UI 中出现不必要的延迟和“抖动”。
passive: true
选项告诉浏览器处理程序不会取消滚动。然后,浏览器立即滚动,提供最大程度的流畅体验,并且事件会按方式处理。
对于某些浏览器(Firefox、Chrome),touchstart
和 touchmove
事件的 passive
默认值为 true
。
event.defaultPrevented
如果阻止了默认操作,则属性 event.defaultPrevented
为 true
,否则为 false
。
它有一个有趣的用例。
还记得在 冒泡和捕获 一章中,我们讨论了 event.stopPropagation()
以及为什么停止冒泡很糟糕?
有时我们可以改用 event.defaultPrevented
,以向其他事件处理程序发出信号,表明事件已处理。
我们来看一个实际的例子。
默认情况下,浏览器在 contextmenu
事件(右键单击)上显示带有标准选项的上下文菜单。我们可以防止它显示并显示我们自己的,如下所示
<button>Right-click shows browser context menu</button>
<button oncontextmenu="alert('Draw our menu'); return false">
Right-click shows our context menu
</button>
现在,除了该上下文菜单之外,我们还想实现文档范围的上下文菜单。
右键单击时,应显示最接近的上下文菜单。
<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
问题在于,当我们单击 elem
时,我们会得到两个菜单:按钮级菜单和(事件冒泡)文档级菜单。
如何解决?一种解决方案是像这样思考:“当我们在按钮处理程序中处理右键单击时,让我们停止它的冒泡”并使用 event.stopPropagation()
<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
现在按钮级菜单按预期工作。但代价很高。我们永远拒绝任何外部代码(包括收集统计信息的计数器等)访问有关右键单击的信息。这是不明智的。
另一种解决方案是在 document
处理程序中检查是否阻止了默认操作?如果是这样,则事件已处理,我们无需对其做出反应。
<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Document context menu");
};
</script>
现在所有内容也都能正常工作了。如果我们有嵌套元素,并且每个元素都有自己的上下文菜单,那也能正常工作。只需确保在每个 contextmenu
处理程序中检查 event.defaultPrevented
。
正如我们清楚地看到的那样,event.stopPropagation()
和 event.preventDefault()
(也称为 return false
)是两件不同的事情。它们彼此无关。
还有其他方法来实现嵌套上下文菜单。其中一种方法是拥有一个带有 document.oncontextmenu
处理程序的全局对象,以及允许我们存储其他处理程序的方法。
该对象将捕获任何右键单击,查看存储的处理程序并运行适当的处理程序。
但是,每个想要上下文菜单的代码段都应该知道该对象,并使用其帮助而不是自己的 contextmenu
处理程序。
摘要
有很多默认的浏览器操作
mousedown
– 开始选择(移动鼠标进行选择)。click
on<input type="checkbox">
– 选中/取消选中input
。submit
– 单击<input type="submit">
或在表单字段内按 Enter 会导致此事件发生,并且浏览器随后提交表单。keydown
– 按键可能会导致在字段中添加字符或执行其他操作。contextmenu
– 该事件在右键单击时发生,操作是显示浏览器上下文菜单。- …还有更多…
如果我们想要通过 JavaScript 独占处理事件,则可以阻止所有默认操作。
要阻止默认操作 - 使用 event.preventDefault()
或 return false
。第二个方法仅适用于使用 on<event>
分配的处理程序。
addEventListener
的 passive: true
选项告诉浏览器不会阻止该操作。这对于某些移动事件(如 touchstart
和 touchmove
)很有用,可以告诉浏览器在滚动之前不必等待所有处理程序完成。
如果阻止了默认操作,则 event.defaultPrevented
的值变为 true
,否则为 false
。
从技术上讲,通过阻止默认操作并添加 JavaScript,我们可以自定义任何元素的行为。例如,我们可以让链接 <a>
像按钮一样工作,而按钮 <button>
则像链接一样(重定向到另一个 URL 等)。
但我们通常应该保留 HTML 元素的语义含义。例如,<a>
应该执行导航,而不是按钮。
除了“仅仅是一件好事”之外,这还让你的 HTML 在可访问性方面变得更好。
此外,如果我们考虑 <a>
的示例,请注意:浏览器允许我们在新窗口中打开此类链接(通过右键单击它们和其他方式)。人们喜欢这样。但是,如果我们使用 JavaScript 让按钮像链接一样工作,甚至使用 CSS 让它看起来像链接,那么 <a>
特有的浏览器功能仍然无法对其工作。
评论
<code>
标记,对于多行 - 将它们包装在<pre>
标记中,对于 10 行以上 - 使用沙盒 (plnkr,jsbin,codepen…)