- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
useActiveElement是 VueUse 中一个轻量的 Elements 类组合式函数,它以响应式的方式暴露document.activeElement,并在页面焦点变化时自动更新返回值。无论你是在构建无障碍提示、快捷键面板、自定义表单组件,还是需要感知"用户此刻正在与哪个 DOM 节点交互",useActiveElement都能以极少的代码提供可靠的焦点追踪能力。读完本文,你将掌握它的基础用法、Shadow DOM 穿透策略、元素移除监听以及组件式调用方式。
快速上手
useActiveElement的用法非常直观:调用后得到一个ShallowRef,其值始终指向当前文档中处于激活状态的元素。
<script setup lang="ts"> import { useActiveElement } from '@vueuse/core' import { watch } from 'vue' const activeElement = useActiveElement() watch(activeElement, (el) => { console.log('focus changed to', el) }) </script>在默认行为下,该函数会在初始化时立即读取一次document.activeElement,并在随后的blur/focus事件中持续同步最新值。官方演示(demo.vue)展示了更贴近实战的场景:页面上排列多个输入框,每次切换焦点时,当前激活元素的data-id都会实时显示出来,这正是它最典型的使用方式。
返回值与响应式原理
从类型定义看,useActiveElement返回的是一个ShallowRef<T | null | undefined>(见 index.ts)。这意味着:
- 它是一个浅层 ref,值本身不会做深层的递归响应式代理,适合存放 DOM 节点这类引用类型;
- 当没有任何元素聚焦(例如焦点落在
document.body或页面本身)时,值可能是null、undefined或body元素,使用前需要做好判空。
源码中更新值的核心逻辑(index.ts)如下:
const getDeepActiveElement = () => { let element = document?.activeElement if (deep) { while (element?.shadowRoot) element = element?.shadowRoot?.activeElement } return element } const trigger = () => { activeElement.value = getDeepActiveElement() as T | null | undefined }监听事件时采用了capture: true与passive: true两个监听选项(index.ts):
- capture(捕获阶段监听):让焦点事件在最早期被捕获,避免某些组件内部调用
stopPropagation后导致焦点变化无法被感知; - passive(被动监听):告知浏览器该监听器不会调用
preventDefault,有助于提升滚动与事件处理性能。
一个值得注意的细节是blur事件处理函数中的判断逻辑:
useEventListener( window, 'blur', (event) => { if (event.relatedTarget !== null) return trigger() }, listenerOptions, )blur事件的relatedTarget表示焦点转移到的目标元素。只有当relatedTarget === null(即焦点完全离开了当前文档、而不是在文档内部元素间转移)时才触发更新,避免在焦点于页面内移动时做无意义的重复赋值。
选项(Options)详解
useActiveElement的选项类型为UseActiveElementOptions(index.ts),它继承了 VueUse 通用的ConfigurableWindow与ConfigurableDocumentOrShadowRoot(定义见 _configurable.ts)。完整选项如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
deep | boolean | true | 是否向 Shadow DOM 内部深度查找真正激活的元素 |
triggerOnRemoval | boolean | false | 当前激活元素被移出 DOM 时,是否触发一次更新(内部基于MutationObserver) |
window | Window | defaultWindow | 自定义window实例,例如在 iframe 或测试环境中使用 |
document | DocumentOrShadowRoot | window.document | 自定义document实例或 Shadow Root |
其中window与document均来自 VueUse 的可配置(Configurable)设计体系:在 SSR 环境下,defaultWindow会被定义为undefined(_configurable.ts),因此函数在服务端不会注册任何监听器,也不会抛错,可以安全地在同构应用中调用。
深入 Shadow DOM:deep选项
Web Components 的 Shadow DOM 是封装样式的利器,但也会带来一个经典问题:document.activeElement只返回最外层的 shadow host,而无法直接暴露 shadow 树内部真正获得焦点的元素。
默认情况下deep: true会让useActiveElement通过while (element?.shadowRoot)循环持续下钻,沿着shadowRoot.activeElement一路找到最深层激活的元素。若你想只拿到 shadow host 本身(例如仅需判断"焦点是否落在某个自定义组件上"),将deep设为false即可:
import { useActiveElement } from '@vueuse/core' // Only get the shadow host, not the element inside shadow DOM const activeElement = useActiveElement({ deep: false })对应的浏览器测试(index.browser.test.ts)专门构造了一个attachShadow({ mode: 'open' })的宿主节点,并验证了以下行为:
- 初始化时返回
document.body(index.browser.test.ts); - 元素已处于聚焦状态时再调用函数,能正确初始化捕获到该元素(index.browser.test.ts);
- 传入自定义
document: shadowRoot后,focus shadow 内部的输入框也能被正确追踪(index.browser.test.ts)。
追踪元素移除:triggerOnRemoval选项
浏览器原生存在一个棘手的问题:当当前激活元素被从 DOM 中移除时,document.activeElement通常会回退到body,但原生focus/blur事件可能不会触发,导致响应式状态无法自动刷新。triggerOnRemoval: true正是为弥补这一盲区而设计的:
import { useActiveElement } from '@vueuse/core' const activeElement = useActiveElement({ triggerOnRemoval: true })该选项的底层实现是 VueUse 的另一个独立组合式函数onElementRemoval(onElementRemoval/index.ts)。其原理可概括为三步:
- 通过
watchEffect解包并取得当前激活元素(unrefElement(target)); - 在
document上挂载一个MutationObserver,配置为childList: true, subtree: true,监听整棵 DOM 树的节点增删; - 遍历每条
MutationRecord的removedNodes,若被移除的节点就是目标元素本身或其祖先包含目标元素,则触发回调刷新activeElement的值(onElementRemoval/index.ts)。
测试用例验证了这一行为(index.browser.test.ts):
- 在普通
document下,激活元素被input.remove()移除后,activeElement会更新为document.body; - 在自定义
shadowRoot下,shadowInput被移除后,activeElement更新为null。
注意:triggerOnRemoval依赖MutationObserver,其开销与观察的 DOM 规模相关。对于焦点变化频繁、且不关心元素移除场景的应用,保持默认的false即可获得最佳性能。
组件式用法:UseActiveElement
除了组合式函数,useActiveElement还以组件形式UseActiveElement提供(组件实现见 component.ts,并通过 packages/components/index.ts 对外导出)。组件通过默认插槽暴露响应式的element数据:
<template> <UseActiveElement v-slot="{ element }"> Active element is {{ element?.dataset.id }} </UseActiveElement> </template>从源码可以看出(component.ts),组件接收的 props 与组合式函数的选项一一对应:deep、triggerOnRemoval、window、document,全部透传给内部的useActiveElement(props)。因此所有上述配置能力在组件形态下同样可用,非常适合在纯模板场景(或无需<script setup>的项目)中使用。
与其他组合式函数的联动
useActiveElement是 VueUse 焦点体系中的基础构件,常被其他函数复用。例如useFocusWithin(useFocusWithin/index.ts)就基于它判断焦点是否位于目标元素内部。你可以用类似思路组合出更复杂的行为,比如:
import { useActiveElement } from '@vueuse/core' import { useFocusWithin } from '@vueuse/core' const activeElement = useActiveElement()例如在构建可搜索的输入建议框时,结合useActiveElement判断焦点是否离开了输入框与下拉列表,从而决定是否收起建议面板。
导出位置与适用范围
useActiveElement从@vueuse/core包导出,导出声明位于 packages/core/index.ts;- 组件版
UseActiveElement从@vueuse/components包导出,声明位于 packages/components/index.ts; - 底层依赖
onElementRemoval与useEventListener同样位于 packages/core 目录内,可在需要时独立复用。
该函数属于浏览器环境(Elements 类别)能力:其焦点追踪依赖 DOM 事件,因此主要服务于客户端渲染场景;得益于defaultWindow的 SSR 保护,在同构应用中调用也不会产生副作用(源码标注了@__NO_SIDE_EFFECTS__,见 index.ts)。
总结
useActiveElement用一个简洁的 API 封装了浏览器焦点追踪中最容易被忽视的细节:capture阶段的事件捕获、Shadow DOM 的深度穿透、以及元素移除时的事件盲区补偿。理解其选项背后的实现机制,能帮助你在构建交互复杂的前端应用时,精准把握"用户正在与哪个元素交互"这一关键状态,并写出既简洁又健壮的焦点相关逻辑。
- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
相关推荐
VueUse useActiveElement 实战指南:在 Airi 中响应式追踪焦点元素与 Shadow DOM 深度遍历
VueUse useActiveElement 实战指南:在 Airi 中响应式追踪焦点元素与 Shadow DOM 深度遍历 导读 useActiveElem
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染airi 项目实战:用 VueUse useElementHover 响应式追踪元素悬停状态
airi 项目实战:用 VueUse useElementHover 响应式追踪元素悬停状态 本文以 airi 仓库内 .agents/skills/vueus
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染airi 中基于 VueUse useMouseInElement 的元素内鼠标位置响应式追踪实战指南
airi 中基于 VueUse useMouseInElement 的元素内鼠标位置响应式追踪实战指南 导读 useMouseInElement 是 VueUs
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考