跳到主要内容

聚焦与失焦

焦点在什么时候到来、什么时候离开?​

  • 获得焦点: 用户点击控件、按 Tab、带 autofocus 属性的元素在页面加载时自动获得, 或 JS 调用 focus();
  • 失去焦点: 用户点别处、按 Tab 跳走、或 JS 引起;
  • 语义: 聚焦 = "准备在这里输入数据", 是初始化的好时机; 失焦 = "数据已经输完", 是校验或保存的好时机;
  • 典型用法: 失焦时校验, 重新聚焦时清掉上一次的错误提示;
input.onblur = function () {
if (!this.value.includes("@")) {
this.classList.add("invalid"); // 失焦: 报告问题
}
};
input.onfocus = function () {
this.classList.remove("invalid"); // 聚焦: 让用户重新输入
};
  • 取舍: required、pattern 等输入属性已经能覆盖简单的校验, 只有需要更大灵活度时才写 JS;
  • 提醒: 一般"应该提示错误", 但不该阻止用户先填别的字段;

focus/blur 为什么做不了事件委托?​

  • 根本原因: 这两个事件不冒泡, 在 <form> 上挂 onfocus 永远不会触发;
  • 方案一: 不冒泡但仍会向下经历捕获阶段, 用 addEventListener(type, handler, true) 挂到外层即可;
form.addEventListener("focus", () => form.classList.add("focused"), true);
form.addEventListener("blur", () => form.classList.remove("focused"), true);
  • 方案二: 换成语义相同的 focusin/focusout, 它们会冒泡;
form.addEventListener("focusin", () => form.classList.add("focused"));
form.addEventListener("focusout", () => form.classList.remove("focused"));
  • 限制: focusin/focusout 只能用 addEventListener 注册, 没有 onfocusin 式的属性写法;

focus()/blur() 方法有哪些坑?​

  • 作用: elem.focus() 设置焦点, elem.blur() 取消焦点;
  • 无法阻止失焦: 在 onblur 里调用 event.preventDefault() 无效, 因为 blur 在焦点已经离开之后才触发;
  • 只能事后挽回: 在 onblur 里再调 input.focus() 把焦点抢回来 (Firefox 不支持, 属于浏览器 bug);
input.onblur = function () {
if (!this.value.includes("@")) input.focus(); // 值不合法就拉回焦点
};
  • JS 自身造成的失焦容易被忽略:
    • alert: 把焦点拉到自己身上, 于是元素失焦; 关闭后焦点又回来 (触发 focus);
    • 元素被移出 DOM: 也造成失焦; 之后重新插入, 焦点不会自动回来;
  • 后果: focus/blur 处理程序会在不该触发的时候触发; 想只跟踪"用户主动失焦", 就别自己制造失焦;

怎样让默认不可聚焦的元素变得可聚焦?​

  • 默认可聚焦: 用户能交互的元素, 如 <button>、<input>、<select>、<a> 等;
  • 默认不可聚焦: <div>、<span>、<table> 这类纯排版元素, 对其调用 focus() 无效, focus/blur 也永不触发;
  • 解法: 加 tabindex 属性; 值即 Tab 切换时的次序号;
取值含义
1、2 …正数排在最前, 按数值升序切换
0排进"无 tabindex"那一组, 即排在所有正数之后, 保持文档顺序
-1Tab 跳过, 只允许用 focus() 程序化聚焦
不带 tabindex按文档源顺序切换
  • 切换顺序示例: <li tabindex="1"> → <li tabindex="2"> → <li tabindex="0">;
  • 附带效果: 加了 tabindex 后该元素同时获得 focus 事件与 :focus 样式;
  • JS 等价操作: elem.tabIndex = 0;

当前焦点落在哪个元素上?​

  • document.activeElement: 读到当前聚焦的元素;