2022 年 1 月 21 日

浏览器默认操作

许多事件会自动导致浏览器执行某些操作。

例如

  • 点击链接 - 启动导航到其 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` 是一个异常

通常会忽略事件处理程序返回的值。

唯一的例外是从使用 `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),touchstarttouchmove 事件的 passive 默认值为 true

event.defaultPrevented

如果阻止了默认操作,则属性 event.defaultPreventedtrue,否则为 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()

正如我们清楚地看到的那样,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> 分配的处理程序。

addEventListenerpassive: true 选项告诉浏览器不会阻止该操作。这对于某些移动事件(如 touchstarttouchmove)很有用,可以告诉浏览器在滚动之前不必等待所有处理程序完成。

如果阻止了默认操作,则 event.defaultPrevented 的值变为 true,否则为 false

保持语义,不要滥用

从技术上讲,通过阻止默认操作并添加 JavaScript,我们可以自定义任何元素的行为。例如,我们可以让链接 <a> 像按钮一样工作,而按钮 <button> 则像链接一样(重定向到另一个 URL 等)。

但我们通常应该保留 HTML 元素的语义含义。例如,<a> 应该执行导航,而不是按钮。

除了“仅仅是一件好事”之外,这还让你的 HTML 在可访问性方面变得更好。

此外,如果我们考虑 <a> 的示例,请注意:浏览器允许我们在新窗口中打开此类链接(通过右键单击它们和其他方式)。人们喜欢这样。但是,如果我们使用 JavaScript 让按钮像链接一样工作,甚至使用 CSS 让它看起来像链接,那么 <a> 特有的浏览器功能仍然无法对其工作。

任务

重要性:3

为什么在下面的代码中 return false 完全不起作用?

<script>
  function handler() {
    alert( "..." );
    return false;
  }
</script>

<a href="https://w3.org" onclick="handler()">the browser will go to w3.org</a>

浏览器在单击时会遵循 URL,但我们不希望它这样做。

如何修复?

当浏览器读取 on* 属性(如 onclick)时,它会从其内容创建处理程序。

对于 onclick="handler()" 函数将是

function(event) {
  handler() // the content of onclick
}

现在我们可以看到,handler() 返回的值没有被使用,并且不影响结果。

修复很简单

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a href="https://w3.org" onclick="return handler()">w3.org</a>

我们还可以使用 event.preventDefault(),如下所示

<script>
  function handler(event) {
    alert("...");
    event.preventDefault();
  }
</script>

<a href="https://w3.org" onclick="handler(event)">w3.org</a>
重要性:5

id="contents" 元素内的所有链接询问用户是否真的要离开。如果他们不离开,则不执行后续操作。

如下所示

详细信息

  • 元素内的 HTML 可能会在任何时候动态加载或重新生成,因此我们无法找到所有链接并在它们上面放置处理程序。使用事件委托。
  • 内容可能包含嵌套标签。在链接中也是如此,例如 <a href=".."><i>...</i></a>

为任务打开沙盒。

这是事件委托模式的一个很好的用途。

在现实生活中,我们可以向服务器发送“日志”请求,而不是询问,该请求会保存有关访问者离开位置的信息。或者,我们可以加载内容并直接在页面中显示它(如果允许)。

我们只需要捕获 contents.onclick 并使用 confirm 向用户询问。一个好主意是使用 link.getAttribute('href') 而不是 link.href 来获取 URL。有关详细信息,请参阅解决方案。

在沙盒中打开解决方案。

重要性:5

创建一个图片库,其中主图片通过点击缩略图来更改。

如下所示

P.S. 使用事件委托。

为任务打开沙盒。

解决方案是将处理程序分配给容器并跟踪点击。如果点击 <a> 链接,则将 #largeImgsrc 更改为缩略图的 href

在沙盒中打开解决方案。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题 或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 - 将它们包装在 <pre> 标记中,对于 10 行以上 - 使用沙盒 (plnkrjsbincodepen…)