前端做无障碍

5 分钟
33 阅读

无障碍

要精准控制任意元素(如 button/a 标签)的聚焦行为(包括「阻止默认聚焦」「主动控制聚焦/失焦」「自定义聚焦时机」),核心是通过 事件拦截、焦点API、tabindex控制 实现,以下是针对 button/a 标签的通用解决方案,结合你的代码示例详细说明:

一、核心需求拆解

你可能需要的聚焦控制场景:

  1. 阻止元素被默认自动聚焦(比如弹窗打开时按钮被自动选中);
  2. 手动控制元素聚焦/失焦(比如点击其他区域让按钮失焦);
  3. 自定义聚焦触发时机(比如仅用户Tab遍历/点击时聚焦,而非程序自动聚焦);
  4. 保留无障碍性(聚焦样式、键盘触发逻辑不丢失)。

二、针对 button/a 标签的聚焦控制方案

场景1:阻止元素被「自动聚焦」(核心需求)

如果按钮/链接被程序(如弹窗打开、路由跳转)自动聚焦,通过以下方式拦截:

方案1:监听 focus 事件,阻止默认行为
tsx 复制代码
// 你的按钮代码改造(React示例)
import { useRef } from 'react';

function AuthButton() {
  const btnRef = useRef<HTMLButtonElement>(null);

  // 阻止自动聚焦(保留用户手动聚焦)
  const handleFocus = (e: React.FocusEvent<HTMLButtonElement>) => {
    // 判断聚焦触发源:仅拦截“程序自动聚焦”,保留“用户手动聚焦(Tab/点击)”
    if (e.nativeEvent.explicitOriginalTarget !== btnRef.current) {
      e.preventDefault();
      // 可选:强制失焦
      btnRef.current?.blur();
    }
  };

  return (
    <button
      ref={btnRef}
      type="button"
      className="B3-1-M-14 text-vi-500 cursor-pointer hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-vi-500 focus:ring-offset-2 rounded"
      onClick={authenticated ? handleLogOut : toLogin}
      onKeyDown={handleAuthKeyDown}
      aria-label={authenticated ? t("log-out") : t("sign-in")}
      onFocus={handleFocus} // 核心:拦截聚焦事件
      // 可选:临时设置tabindex=-1(阻止Tab遍历,需配合逻辑恢复)
      // tabindex={isPreventFocus ? -1 : 0}
    >
      {authenticated ? t("log-out") : t("sign-in")}
    </button>
  );
}
方案2:通过 tabindex 临时禁用聚焦(更简单)
  • tabindex="-1":元素不可被Tab遍历聚焦,但仍可通过 element.focus() 手动聚焦;
  • tabindex="0":恢复可聚焦(默认值);
  • 适合「临时阻止聚焦,需要时恢复」的场景:
tsx 复制代码
// 控制 tabindex 实现聚焦开关
const [preventFocus, setPreventFocus] = useState(true); // 初始阻止聚焦

return (
  <button
    tabindex={preventFocus ? -1 : 0} // 核心:控制tabindex
    onFocus={(e) => preventFocus && e.preventDefault()}
    // 比如:用户点击后允许聚焦
    onClick={() => {
      setPreventFocus(false);
      authenticated ? handleLogOut() : toLogin();
    }}
    // 其他属性不变
  >
    {authenticated ? t("log-out") : t("sign-in")}
  </button>
);

场景2:手动控制元素「聚焦/失焦」

主动让按钮/链接聚焦或失焦(比如弹窗关闭时让按钮失焦,弹窗打开时让按钮聚焦):

tsx 复制代码
function AuthButton() {
  const btnRef = useRef<HTMLButtonElement>(null);

  // 手动聚焦按钮
  const focusBtn = () => {
    btnRef.current?.focus({ preventScroll: true }); // preventScroll:聚焦时不滚动页面
  };

  // 手动让按钮失焦
  const blurBtn = () => {
    btnRef.current?.blur();
  };

  // 示例:弹窗打开时失焦按钮,弹窗关闭时聚焦按钮
  useEffect(() => {
    if (popupOpen) {
      blurBtn(); // 弹窗打开 → 按钮失焦
    } else {
      focusBtn(); // 弹窗关闭 → 按钮聚焦
    }
  }, [popupOpen]);

  return (
    <button
      ref={btnRef} // 核心:绑定ref
      // 其他属性不变
    >
      {authenticated ? t("log-out") : t("sign-in")}
    </button>
  );
}

场景3:仅保留「用户主动操作」的聚焦(屏蔽程序自动聚焦)

通过判断聚焦的「触发类型」,仅允许用户手动操作(Tab/点击/回车)触发聚焦,屏蔽程序自动聚焦:

tsx 复制代码
const handleFocus = (e: React.FocusEvent<HTMLButtonElement>) => {
  // 核心:判断聚焦是否由用户显式操作触发
  // nativeEvent.isTrusted:是否为用户行为(而非JS触发)
  if (!e.nativeEvent.isTrusted) {
    e.preventDefault();
    e.target.blur();
  }
};

// 按钮添加 onFocus={handleFocus}
<button
  onFocus={handleFocus}
  // 其他属性不变
>
  {authenticated ? t("log-out") : t("sign-in")}
</button>

场景4:针对 <a> 标签的适配(和button逻辑一致)

<a> 标签的聚焦控制和 button 完全通用,仅需替换ref类型:

tsx 复制代码
const linkRef = useRef<HTMLAnchorElement>(null);

// 阻止自动聚焦
const handleLinkFocus = (e: React.FocusEvent<HTMLAnchorElement>) => {
  if (!e.nativeEvent.isTrusted) {
    e.preventDefault();
    linkRef.current?.blur();
  }
};

return (
  <a
    ref={linkRef}
    href="/login"
    onFocus={handleLinkFocus}
    tabindex={preventFocus ? -1 : 0}
  >
    {t("sign-in")}
  </a>
);

三、关键注意事项(无障碍+兼容性)

  1. 保留聚焦样式:禁止直接去掉 focus 相关样式(如 focus:ring-2),否则键盘用户无法感知焦点位置;
  2. tabindex 慎用
    • 不要全局设置 tabindex=-1,仅临时阻止聚焦时使用;
    • 避免 tabindex>0,会破坏默认Tab遍历顺序;
  3. isTrusted 兼容性:所有现代浏览器支持,IE11及以下不支持(可忽略,或降级为 e.target === document.activeElement);
  4. React 合成事件:React的 onFocus 是合成事件,需通过 nativeEvent 访问原生事件属性(如 isTrusted)。

四、总结(按需求选方案)

需求场景 推荐方案
阻止自动聚焦 方案1(onFocus拦截) + 方案2(tabindex控制)
手动控制聚焦/失焦 方案2(ref + focus()/blur())
仅保留用户主动聚焦 方案3(isTrusted判断)
<a>/<button> 通用 上述方案完全复用,仅替换ref类型

结合你的代码,最直接的改造是:给按钮绑定ref + 监听onFocus事件拦截自动聚焦,既保留用户手动操作的聚焦(符合无障碍),又阻止程序默认的自动聚焦。

评论

评论

发表评论