首页
/ Svelte `<svelte:document>` 详解:Document 级事件监听、属性绑定与 Attachments 用法

Svelte `<svelte:document>` 详解:Document 级事件监听、属性绑定与 Attachments 用法

2026-09-06 23:51:13作者:傅爽业Veleda

<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> 的处理分为分析与转换两个阶段:

  1. 分析阶段visitors/SvelteDocument.js 对该节点执行两项校验:
    • 调用 disallow_children 禁止其携带任何子节点;
    • 遍历属性:事件属性(onevent={handler} 形式)会经过 check_global_event_reference 检查;而任何普通属性或展开属性都会直接抛出 illegal_element_attribute 编译错误。
  2. 转换阶段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);
		}
	});
}

可以归纳出三点行为保证:

  1. 立即同步一次call_handler_immediately 默认为 true,挂载时会先执行一次 handler,因此组件挂载后立即拿到 document 当前属性值;
  2. 自动清理:通过 teardown 注册反注册逻辑,当组件的渲染 effect 被销毁时自动 removeEventListener——这与 <svelte:window> 文档中"无需担心组件销毁时移除监听器"的承诺一致,你在 <svelte:document> 上声明的 onevent 监听同样享受此机制;
  3. 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 的变化在 onetwoBODY 之间同步更新。

对 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 上触发的事件)
可绑定属性 innerWidthinnerHeightouterWidthouterHeightscrollXscrollYonlinedevicePixelRatio activeElementfullscreenElementpointerLockElementvisibilityState
双向绑定 scrollXscrollY 可写 无(全部只读)
位置限制 仅组件顶层,不能在块/元素内 仅组件顶层,不能在块/元素内
子节点 不允许 不允许(disallow_children 校验)
普通属性/展开 不支持 不支持(illegal_element_attribute 错误)

两者在 bindings.js 中成对定义,且均被 invalid_elements: ['svelte:window', 'svelte:document'] 排除在 clientWidth/offsetWidth/innerText 等通用 DOM 绑定之外——即不能在 <svelte:document> 上使用这些绑定,编译器会报错。

总结

  • <svelte:document> 是接入 document 级事件的声明式入口:onvisibilitychangeonfullscreenchangeonpointerlockchange 等写法与组件内其他事件属性一致,且监听器随组件销毁自动清理;
  • 仅支持四个只读绑定:activeElementfullscreenElementpointerLockElementvisibilityState,全部 omit_in_ssr,只在客户端生效;
  • 只能位于组件顶层,不能有子节点,不能携带普通属性或 {...spread} 展开;事件处理函数会经过全局事件引用校验;
  • 结合 {@attach},它也是把自定义附件逻辑挂到 document 上的标准位置。

如需查看完整行为回归,可浏览 tests/runtime-legacy/samplesdocument-binding-* 相关目录,以及 tests/validator/samplesillegal_spread_element-documentdocument-binding-invalid-dimensions 等校验用例。

登录后查看全文
热门项目推荐
相关项目推荐