Svelte `<svelte:document>` 详解:Document 级事件监听、属性绑定与 Attachments 用法
<svelte:document> 是 Svelte 提供的特殊元素,用于在组件中直接监听挂载在 document 上的事件(如只触发于 document 的 visibilitychange)、绑定 document 的只读属性(如 visibilityState),以及对 document 使用 attachments。本文基于 Svelte 官方文档与编译器/运行时源码,梳理其完整语法、可用的四个绑定属性、编译期校验规则,以及底层监听与清理机制,帮助你在页面可见性检测、全屏状态跟踪、指针锁定等场景中以声明式方式接入 document 级行为。
基本语法与定位
官方文档给出的两种基本用法如下:
<svelte:document onevent={handler} />
<svelte:document bind:prop={value} />
与 <svelte:window> 类似,<svelte:document> 存在的意义是:有些事件只触发在 document 上(典型如 visibilitychange),无法通过 <svelte:window> 捕获;同时它也允许对 document 使用 attachments({@attach})。
文档中明确的约束是:
As with
<svelte:window>, this element may only appear the top level of your component and must never be inside a block or element.
即 <svelte:document> 只能出现在组件的顶层,不能位于任何块({#if}、{#each} 等)或元素内部。
一个同时使用事件监听与 attachment 的完整示例:
<svelte:document onvisibilitychange={handleVisibilityChange} {@attach someAttachment} />
事件监听:从模板到运行时
编译器如何处理
从源码结构看,<svelte:document> 的处理分为分析与转换两个阶段:
- 分析阶段:visitors/SvelteDocument.js 对该节点执行两项校验:
- 调用
disallow_children禁止其携带任何子节点; - 遍历属性:事件属性(
onevent={handler}形式)会经过check_global_event_reference检查;而任何普通属性或展开属性都会直接抛出illegal_element_attribute编译错误。
- 调用
- 转换阶段:3-transform 客户端的 SvelteDocument 访问器 将其交由通用的
visit_special_element(node, '$.document', context)处理——事件属性会被生成为初始化语句,直接以document作为事件目标注册监听,而不是渲染出任何真实 DOM 节点。
错误边界(可验证)
仓库中的校验测试印证了上述规则。例如 illegal_spread_element-document:
<script>
let a = {};
</script>
<svelte:document {...a}></svelte:document>
对应的期望错误为:
{
"code": "illegal_element_attribute",
"message": "`<svelte:document>` does not support non-event attributes or spread attributes"
}
这说明 <svelte:document> 只接受事件属性与 bind: 指令,既不支持普通属性,也不支持 {...spread} 展开;类似地,document-binding-invalid-dimensions 等测试用例验证了在其上绑定 clientWidth 等尺寸属性同样会被拒绝。
可绑定的四个属性(全部只读)
文档列出了 <svelte:document> 支持绑定的属性,且均为只读:
| 属性 | 对应的 document 属性 | 变更通知事件(源码标注) |
|---|---|---|
activeElement |
document.activeElement |
通过 focusin/focusout 监听(见下文) |
fullscreenElement |
document.fullscreenElement |
fullscreenchange |
pointerLockElement |
document.pointerLockElement |
pointerlockchange |
visibilityState |
document.visibilityState |
visibilitychange |
绑定配置在源码中的定义
这四个属性的元数据集中定义在 binding_properties 中,且都以 valid_elements: ['svelte:document'] 限定为仅可用于该特殊元素,并统一标注 omit_in_ssr: true:
// document
activeElement: {
valid_elements: ['svelte:document'],
omit_in_ssr: true
},
fullscreenElement: {
valid_elements: ['svelte:document'],
event: 'fullscreenchange',
omit_in_ssr: true
},
pointerLockElement: {
valid_elements: ['svelte:document'],
event: 'pointerlockchange',
omit_in_ssr: true
},
visibilityState: {
valid_elements: ['svelte:document'],
event: 'visibilitychange',
omit_in_ssr: true
}
两个值得注意的点:
omit_in_ssr: true意味着这些绑定在服务端渲染时会被省略——服务器端没有document对象,绑定只发生在客户端水合/挂载后生效;- 与
<svelte:window>的scrollX/scrollY不同(二者是bidirectional: true),document 的四个绑定都没有bidirectional标志,与文档"All are readonly"的表述一致:绑定只会把 DOM 上的值同步到你的变量,而不会反向写入document。
运行时实现:监听、同步与自动清理
绑定运行时的核心在 bindings/document.js:
export function bind_active_element(update) {
listen(document, ['focusin', 'focusout'], (event) => {
if (event && event.type === 'focusout' && /** @type {FocusEvent} */ (event).relatedTarget) {
// The tests still pass if we remove this, because of JSDOM limitations, but it is necessary
// to avoid temporarily resetting to `document.body`
return;
}
update(document.activeElement);
});
}
这里有一个源码注释解释的边界处理:focusout 若带有 relatedTarget(即焦点正转移到另一个元素),会被跳过,以避免焦点在转移过程中被临时错误地回退到 document.body,导致绑定的值出现"闪烁"。
其余属性(fullscreenElement 等)则由通用机制根据 binding_properties 中的 event 字段订阅对应事件,并在事件触发时读取 document 上对应属性、调用 update 同步到响应式变量。
这些监听都依赖 listen 辅助函数:
export function listen(target, events, handler, call_handler_immediately = true) {
if (call_handler_immediately) {
handler();
}
for (var name of events) {
target.addEventListener(name, handler);
}
teardown(() => {
for (var name of events) {
target.removeEventListener(name, handler);
}
});
}
可以归纳出三点行为保证:
- 立即同步一次:
call_handler_immediately默认为true,挂载时会先执行一次 handler,因此组件挂载后立即拿到document当前属性值; - 自动清理:通过
teardown注册反注册逻辑,当组件的渲染 effect 被销毁时自动removeEventListener——这与<svelte:window>文档中"无需担心组件销毁时移除监听器"的承诺一致,你在<svelte:document>上声明的onevent监听同样享受此机制; - SSR 安全:与
<svelte:window>一样,不需要手写typeof window !== 'undefined'之类的存在性检查。
运行期测试用例
仓库中的运行时测试覆盖了真实用法,可作参考:
<script>
export let fullscreen;
</script>
<svelte:document bind:fullscreenElement={fullscreen}/>
<div></div>
<script>
let active;
$: console.log(active?.id || active?.nodeName || '...');
</script>
<svelte:document bind:activeElement={active} />
<button id="one">one</button>
<button id="two">two</button>
点击两个按钮时,active 会随 document.activeElement 的变化在 one、two 与 BODY 之间同步更新。
对 document 使用 Attachments
除了事件与绑定,<svelte:document> 还支持 Svelte 5 的 attachments 语法 {@attach},将自定义附件逻辑(如 use 机制风格的封装)直接挂到 document 上。文档给出的组合示例即:
<svelte:document onvisibilitychange={handleVisibilityChange} {@attach someAttachment} />
attachment 与 <svelte:document> 事件监听共存于同一顶层节点,二者互不干扰;attachment 的完整语法与返回值约定见 09-@attach.md。
与 <svelte:window> 的差异对比
| 维度 | <svelte:window> |
<svelte:document> |
|---|---|---|
| 事件目标 | window |
document(可捕获 visibilitychange 等仅在 document 上触发的事件) |
| 可绑定属性 | innerWidth、innerHeight、outerWidth、outerHeight、scrollX、scrollY、online、devicePixelRatio |
activeElement、fullscreenElement、pointerLockElement、visibilityState |
| 双向绑定 | scrollX、scrollY 可写 |
无(全部只读) |
| 位置限制 | 仅组件顶层,不能在块/元素内 | 仅组件顶层,不能在块/元素内 |
| 子节点 | 不允许 | 不允许(disallow_children 校验) |
| 普通属性/展开 | 不支持 | 不支持(illegal_element_attribute 错误) |
两者在 bindings.js 中成对定义,且均被 invalid_elements: ['svelte:window', 'svelte:document'] 排除在 clientWidth/offsetWidth/innerText 等通用 DOM 绑定之外——即不能在 <svelte:document> 上使用这些绑定,编译器会报错。
总结
<svelte:document>是接入 document 级事件的声明式入口:onvisibilitychange、onfullscreenchange、onpointerlockchange等写法与组件内其他事件属性一致,且监听器随组件销毁自动清理;- 仅支持四个只读绑定:
activeElement、fullscreenElement、pointerLockElement、visibilityState,全部omit_in_ssr,只在客户端生效; - 只能位于组件顶层,不能有子节点,不能携带普通属性或
{...spread}展开;事件处理函数会经过全局事件引用校验; - 结合
{@attach},它也是把自定义附件逻辑挂到document上的标准位置。
如需查看完整行为回归,可浏览 tests/runtime-legacy/samples 下 document-binding-* 相关目录,以及 tests/validator/samples 中 illegal_spread_element-document、document-binding-invalid-dimensions 等校验用例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00