2022 年 6 月 19 日

聚焦:focus/blur

当用户点击元素或使用键盘上的 Tab 键时,元素会获得焦点。还有一个 autofocus HTML 属性,它可以在页面加载时默认将焦点放在元素上,以及其他获取焦点的方法。

聚焦元素通常意味着:“准备在此处接受数据”,因此这是我们可以运行代码以初始化所需功能的时刻。

失去焦点(“模糊”)的时刻可能更为重要。那是当用户单击其他位置或按 Tab 键转到下一个表单字段时,或者还有其他方法。

失去焦点通常意味着:“数据已输入”,因此我们可以运行代码来检查它,甚至可以将其保存到服务器等。

在处理焦点事件时有一些重要的特点。我们将尽力进一步介绍它们。

事件 focus/blur

focus 事件在聚焦时调用,blur 事件在元素失去焦点时调用。

让我们用它们来验证输入字段。

在下面的示例中

  • blur 处理程序检查字段中是否输入了电子邮件,如果没有,则显示错误。
  • focus 处理程序隐藏错误消息(在 blur 中将再次检查)
<style>
  .invalid { border-color: red; }
  #error { color: red }
</style>

Your email please: <input type="email" id="input">

<div id="error"></div>

<script>
input.onblur = function() {
  if (!input.value.includes('@')) { // not email
    input.classList.add('invalid');
    error.innerHTML = 'Please enter a correct email.'
  }
};

input.onfocus = function() {
  if (this.classList.contains('invalid')) {
    // remove the "error" indication, because the user wants to re-enter something
    this.classList.remove('invalid');
    error.innerHTML = "";
  }
};
</script>

现代 HTML 允许我们使用输入属性执行许多验证:requiredpattern 等。有时它们正是我们需要的。当我们需要更大的灵活性时,可以使用 JavaScript。此外,如果值正确,我们可以自动将更改后的值发送到服务器。

方法 focus/blur

方法 elem.focus()elem.blur() 设置/取消元素上的焦点。

例如,如果值无效,则让访问者无法离开输入

<style>
  .error {
    background: red;
  }
</style>

Your email please: <input type="email" id="input">
<input type="text" style="width:220px" placeholder="make email invalid and try to focus here">

<script>
  input.onblur = function() {
    if (!this.value.includes('@')) { // not email
      // show the error
      this.classList.add("error");
      // ...and put the focus back
      input.focus();
    } else {
      this.classList.remove("error");
    }
  };
</script>

它在除 Firefox 之外的所有浏览器中均有效(bug)。

如果我们在输入中输入内容,然后尝试使用 Tab 或单击 <input> 之外,则 onblur 会将焦点返回。

请注意,我们无法通过在 onblur 中调用 event.preventDefault() 来“阻止失去焦点”,因为 onblur 在元素失去焦点之后才起作用。

但在实践中,在实现类似功能之前,应该仔细考虑,因为我们通常应该向用户显示错误,但不应阻止他们在填写表单中的进度。他们可能希望先填写其他字段。

由 JavaScript 引发的焦点丢失

焦点丢失可能出于多种原因。

其中之一是访问者单击其他位置时。但 JavaScript 本身也可能导致这种情况,例如

  • alert 会将焦点移到自身,因此它会导致元素(blur 事件)失去焦点,当 alert 被关闭时,焦点会返回(focus 事件)。
  • 如果从 DOM 中删除元素,则也会导致焦点丢失。如果稍后重新插入,则焦点不会返回。

这些功能有时会导致 focus/blur 处理程序行为不当,即在不需要时触发。

最好的方法是在使用这些事件时要小心。如果我们想要跟踪用户发起的焦点丢失,那么我们应该避免自己造成这种情况。

允许对任何元素进行聚焦:tabindex

默认情况下,许多元素不支持聚焦。

列表在不同浏览器之间略有不同,但有一点始终正确:访问者可以与之交互的元素保证支持 focus/blur<button><input><select><a> 等。

另一方面,用于格式化内容的元素,例如 <div><span><table>,默认情况下不可聚焦。方法 elem.focus() 对它们不起作用,并且永远不会触发 focus/blur 事件。

这可以通过 HTML 属性 tabindex 更改。

任何元素只要有 tabindex 就可以获得焦点。属性的值是当使用 Tab(或类似的东西)在元素之间切换时的元素顺序号。

也就是说:如果我们有两个元素,第一个有 tabindex="1",第二个有 tabindex="2",那么在第一个元素中按 Tab 会将焦点移到第二个元素。

切换顺序是:首先是 tabindex1 及以上(按 tabindex 顺序)的元素,然后是没有 tabindex 的元素(例如常规 <input>)。

没有匹配 tabindex 的元素按文档源顺序(默认顺序)切换。

有两个特殊值

  • tabindex="0" 将元素放在没有 tabindex 的元素中。也就是说,当我们切换元素时,tabindex=0 的元素在 tabindex ≥ 1 的元素之后。

    通常用于使元素可获得焦点,但保留默认切换顺序。使元素成为与 <input> 相当的表单的一部分。

  • tabindex="-1" 仅允许对元素进行编程聚焦。 Tab 键忽略此类元素,但方法 elem.focus() 有效。

例如,这里有一个列表。单击第一个项目并按 Tab

Click the first item and press Tab. Keep track of the order. Please note that many subsequent Tabs can move the focus out of the iframe in the example.
<ul>
  <li tabindex="1">One</li>
  <li tabindex="0">Zero</li>
  <li tabindex="2">Two</li>
  <li tabindex="-1">Minus one</li>
</ul>

<style>
  li { cursor: pointer; }
  :focus { outline: 1px dashed green; }
</style>

顺序如下:1 - 2 - 0。通常,<li> 不支持聚焦,但 tabindex 完全支持它,以及事件和使用 :focus 的样式。

属性 elem.tabIndex 也适用

我们可以使用 elem.tabIndex 属性通过 JavaScript 添加 tabindex。效果相同。

委托:focusin/focusout

事件 focusblur 不冒泡。

例如,我们不能在 <form> 上放置 onfocus 来突出显示它,如下所示

<!-- on focusing in the form -- add the class -->
<form onfocus="this.className='focused'">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>

<style> .focused { outline: 1px solid red; } </style>

上面的示例不起作用,因为当用户聚焦于 <input> 时,focus 事件仅触发该输入。它不会冒泡。因此 form.onfocus 永远不会触发。

有两个解决方案。

首先,有一个有趣的历史特性:focus/blur 不会冒泡,但会在捕获阶段向下传播。

这将起作用

<form id="form">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  // put the handler on capturing phase (last argument true)
  form.addEventListener("focus", () => form.classList.add('focused'), true);
  form.addEventListener("blur", () => form.classList.remove('focused'), true);
</script>

其次,有 focusinfocusout 事件——与 focus/blur 完全相同,但它们会冒泡。

请注意,必须使用 elem.addEventListener 分配它们,而不是 on<event>

所以,这是另一个可行的变体

<form id="form">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  form.addEventListener("focusin", () => form.classList.add('focused'));
  form.addEventListener("focusout", () => form.classList.remove('focused'));
</script>

摘要

事件focusblur在元素获取/失去焦点时触发。

它们的特殊之处是

  • 它们不会冒泡。可以改为使用捕获状态或focusin/focusout
  • 大多数元素默认情况下不支持焦点。使用tabindex使任何内容可聚焦。

当前聚焦的元素可用作document.activeElement

任务

重要性:5

创建一个单击后变为<textarea><div>

该文本区域允许编辑<div>中的 HTML。

当用户按下Enter或失去焦点时,<textarea>会变回<div>,其内容变为<div>中的 HTML。

在新窗口中演示

为该任务打开沙盒。

重要性:5

使表格单元格在单击时可编辑。

  • 单击时,单元格应变为“可编辑”(内部显示文本区域),我们可以更改 HTML。不应调整大小,所有几何形状应保持不变。
  • 单元格下方会显示确定和取消按钮,用于完成/取消编辑。
  • 一次只能编辑一个单元格。当<td>处于“编辑模式”时,将忽略对其他单元格的单击。
  • 该表格可能包含许多单元格。使用事件委托。

演示

为该任务打开沙盒。

  1. 单击时,用具有相同大小且无边框的<textarea>替换单元格的innerHTML。可以使用 JavaScript 或 CSS 设置正确的大小。
  2. textarea.value设置为td.innerHTML
  3. 聚焦文本区域。
  4. 在单元格下方显示确定/取消按钮,处理对它们的单击。

在沙盒中打开解决方案。

重要性:4

聚焦鼠标。然后使用箭头键移动鼠标

在新窗口中演示

P.S. 不要将事件处理程序放在#mouse元素之外的任何地方。

P.P.S. 不要修改 HTML/CSS,该方法应该是通用的,适用于任何元素。

为该任务打开沙盒。

我们可以使用mouse.onclick处理单击并使用position:fixed使鼠标“可移动”,然后使用mouse.onkeydown处理箭头键。

唯一的缺点是keydown仅触发具有焦点的元素。因此,我们需要向元素添加tabindex。由于我们被禁止更改 HTML,因此我们可以使用mouse.tabIndex属性。

P.S. 我们还可以用mouse.onfocus替换mouse.onclick

在沙盒中打开解决方案。

教程地图

评论

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