☰
VueUse useActiveElement 完全指南:响应式追踪当前聚焦元素
2026/10/1 10:07:36 网站建设 项目流程
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

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)。完整选项如下:

选项类型默认值说明
deepbooleantrue是否向 Shadow DOM 内部深度查找真正激活的元素
triggerOnRemovalbooleanfalse当前激活元素被移出 DOM 时,是否触发一次更新(内部基于MutationObserver)
windowWindowdefaultWindow自定义window实例,例如在 iframe 或测试环境中使用
documentDocumentOrShadowRootwindow.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)。其原理可概括为三步:

  1. 通过watchEffect解包并取得当前激活元素(unrefElement(target));
  2. 在document上挂载一个MutationObserver,配置为childList: true, subtree: true,监听整棵 DOM 树的节点增删;
  3. 遍历每条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

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

相关推荐

上一篇:Home Assistant Overkiz 集成:使用 overkiz.set_cover_position_and_tilt 动作平滑联动调整窗帘位置与倾斜角度
下一篇:es-toolkit/fp 的 initial() 详解:在 pipe 管道中安全剔除数组最后一个元素

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询