news 2026/9/7 9:47:25

Svelte transition 指令完全指南:原理、内置过渡、自定义函数与源码实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Svelte transition 指令完全指南:原理、内置过渡、自定义函数与源码实现解析

Svelte transition 指令完全指南:原理、内置过渡、自定义函数与源码实现解析

【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte

Transition(过渡)是 Svelte 中由"元素因状态变化而进入或离开 DOM"这一事件触发的动画机制。本文基于官方文档 14-transition.md,系统讲解transition:指令的用法、svelte/transition内置过渡的参数细节、自定义过渡函数的规范、过渡事件以及可访问性处理,并深入当前仓库的编译器与运行时源码,剖析"局部 vs 全局"过渡、可逆双向过渡、csstick的底层执行路径,帮助你在掌握 API 的同时理解其实现原理。

基本概念:transition 何时触发

transition由元素进入或离开 DOM触发,而进出 DOM 通常由状态变化引起。两个关键特性:

  • 当一个块(如{#if ...}块)正在"离场"(transitioning out)时,块内所有元素——包括自身没有过渡的元素——都会保留在 DOM 中,直到块内所有过渡全部完成。
  • transition:指令表示一个双向(bidirectional)过渡,即过渡进行到一半时可以平滑反向。

最典型的应用示例:

<script> import { fade } from 'svelte/transition'; let visible = $state(false); </script> <button onclick={() => visible = !visible}>toggle</button> {#if visible} <div transition:fade>fades in and out</div> {/if}

{#if visible}的求值结果变化时,<div>进出 DOM,transition:fade便同时覆盖了入场(intro)和离场(outro)两个方向。

编译器如何生成过渡代码

从源码结构看,编译器在处理过渡指令时会将其编译为运行时内部函数$.transition的调用。见 TransitionDirective.js:

  • 通过位标志编码修饰符:global修饰符置TRANSITION_GLOBAL位;in:TRANSITION_IN位,out:TRANSITION_OUT位,二者同时出现(即transition:)则方向为both
  • 参数以 thunk(惰性求值的箭头函数)形式传入,因此过渡参数表达式可以在运行时反复求值;
  • 调用语句被推入context.state.after_update,源码注释说明这是为了确保它总是在bind:this之后执行,从而保证过渡函数拿到的是正确的节点引用;
  • 若参数表达式是异步的(例如含await),编译器会用$.run_after_blockers包裹该语句,等阻塞表达式完成后再执行过渡初始化。

局部(local)与全局(global)过渡

过渡默认是局部的(local):只有当过渡所属的直接父块被创建或销毁时才播放,而不会因为更外层的父块创建/销毁而播放。加上|global修饰符后,任意祖先块的变化都会触发它。

{#if x} {#if y} <p transition:fade>fades in and out only when y changes</p> <p transition:fade|global>fades in and out when x or y change</p> {/if} {/if}

运行时如何区分二者?在 transitions.js 中,transition()在创建过渡管理器后,若是 intro 且处于"应播放 intro"的场景(should_intro),会向上查找最近的块级 effect:

  • 跳过透明块(snippet、else if块等标记为EFFECT_TRANSPARENT的 effect);
  • 检查该块 effect 是否带有REACTION_RAN标志——即状态变化是否发生在这个块自身,而不是它的祖先。局部过渡只有此时才真正创建 intro effect;
  • 全局过渡则跳过上述判断,直接播放。

这正是"局部过渡只在y变化时播放、|global过渡在xy变化时都播放"的底层依据。

内置过渡:svelte/transition 模块

一系列内置过渡可从svelte/transition模块导入,实现位于 transition/index.js,类型定义见 public.d.ts。全部内置函数签名如下(参数均可选,括号内为源码中的默认值):

过渡函数动画内容特有参数
fade(node, params)透明度从 0 到当前计算值delay=0, duration=400, easing=linear
fly(node, params)x/y 位移 + 透明度x=0, y=0, opacity=0, easing=cubic_out
slide(node, params)沿主轴"展开/收起",同时缩放高度/宽度、padding、margin、borderaxis='y''x' \| 'y'
scale(node, params)缩放 + 透明度start=0, opacity=0, easing=cubic_out
blur(node, params)模糊滤镜 + 透明度amount=5(支持带单位的字符串),opacity=0
draw(node, params)SVG 路径描边动画,要求节点有getTotalLength()speed,或duration=800/duration(len)函数
crossfade(params)生成send/receive一对跨位置过渡,见下文fallback(node, params, intro)

所有内置过渡都遵循统一的TransitionConfig返回结构:

export interface TransitionConfig { delay?: number; duration?: number; easing?: EasingFunction; // (t: number) => number css?: (t: number, u: number) => string; tick?: (t: number, u: number) => void; }

几个实现细节值得注意:

  • fade会先读取getComputedStyle(node).opacity作为目标值,因此元素本身有半透明样式时,过渡是在"当前透明度的 0 到 1"之间进行,而不是硬编码 0→1;
  • fly中位移量使用(1 - t) * value计算,即in过渡(t 从 0 到 1)时从偏移量回到原点,且会保留元素原有的transform
  • slide对每一帧返回overflow: hidden;前缀,并在运行时若检测到关键帧包含overflow: hidden,会以行内样式补上——源码注释指出这是为了规避 Safari 18 之前的 bug(见 transitions.js 的needs_overflow_hidden逻辑);
  • draw通过stroke-dasharraystroke-dashoffset实现"画线"效果,speedduration二选一:给speedduration = len / speed,给函数形式的duration时以路径总长len求值;
  • crossfade返回[send, receive]一对过渡函数,内部用两个Mapkey配对发送方与接收方元素,并基于getBoundingClientRect()计算位移、缩放与透明度插值;若某节点没有对应的"另一半"(列表项消失),则回退到可选的fallback过渡。

过渡参数

过渡可以携带参数。文档特别说明:双花括号{{...}}不是特殊语法,就是表达式标签里的一个对象字面量

{#if visible} <div transition:fade={{ duration: 2000 }}>fades in and out over two seconds</div> {/if}

参数对象在运行时通过get_params?.()惰性求值后传给过渡函数(见$.transitionget_options())。由于是 thunk,参数可以引用响应式状态——例如下一节示例中prefersReducedMotion.current变化时参数会随之更新。

可访问性:prefers-reduced-motion

过渡由Web Animations API驱动,而不是 CSS 过渡/动画,因此一条将transition-durationanimation-duration置零的全局@media (prefers-reduced-motion: reduce)规则对 Svelte 过渡没有效果

正确做法是使用svelte/motion提供的prefersReducedMotion(自 5.7.0 引入)来调整或完全禁用过渡。其实现见 motion/index.js——它本质上是一个订阅'(prefers-reduced-motion: reduce)'媒体查询的MediaQuery响应式对象,.current属性会在系统偏好变化时更新,从而驱动响应式依赖:

<script> import { prefersReducedMotion } from 'svelte/motion'; import { fly } from 'svelte/transition'; let visible = $state(false); </script> {#if visible} <p transition:fly={{ y: prefersReducedMotion.current ? 0 : 200 }}> flies in, unless the user prefers reduced motion </p> {/if}

由于过渡参数是响应式表达式,用户开关"减少动态效果"后,参数会重新求值,后续过渡立即按新参数播放。

自定义过渡函数

自定义过渡函数的完整签名:

transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => { delay?: number, duration?: number, easing?: (t: number) => number, css?: (t: number, u: number) => string, tick?: (t: number, u: number) => void }

其中options是第三个参数,目前包含direction,取值为inoutboth。在运行时源码中可以确认:transition()根据TRANSITION_IN/TRANSITION_OUT标志计算directionin:out:同时存在时为'both'),并作为第三个参数传给过渡函数(见 TransitionDirective.js 生成代码与 transitions.js 的get_options())。

关键语义:

  • 若返回对象带css函数,Svelte 会为其生成 Web Animations 的关键帧;
  • css收到的t是经过easing处理后的 0~1 值。in过渡t从 0 走到 1,out过渡t从 1 走到 0——1永远是元素的"自然状态"(相当于没施加任何过渡),u恒等于1 - t
  • css/tick会在过渡开始前被反复调用(不同的tu),用于预计算关键帧序列。

使用 css 的示例(缩放 + 弹性缓动)

<!--- file: App.svelte ---> <script> import { elasticOut } from 'svelte/easing'; /** @type {boolean} */ export let visible; /** * @param {HTMLElement} node * @param {{ delay?: number, duration?: number, easing?: (t: number) => number }} params */ function whoosh(node, params) { const existingTransform = getComputedStyle(node).transform.replace('none', ''); return { delay: params.delay || 0, duration: params.duration || 400, easing: params.easing || elasticOut, css: (t, u) => `transform: ${existingTransform} scale(${t})` }; } </script> {#if visible} <div in:whoosh>whooshes in</div> {/if}

NOTE:能用css就用css,不要用tick——Web Animations 可以脱离主线程运行,避免在低性能设备上产生卡顿。

使用 tick 的示例(打字机效果)

自定义过渡函数还可以返回tick函数,它在过渡进行中每帧被调用,参数同样是tu

<!--- file: App.svelte ---> <script> export let visible = false; /** * @param {HTMLElement} node * @param {{ speed?: number }} params */ function typewriter(node, { speed = 1 }) { const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE; if (!valid) { throw new Error(`This transition only works on elements with a single text node child`); } const text = node.textContent; const duration = text.length / (speed * 0.01); return { duration, tick: (t) => { const i = ~~(text.length * t); node.textContent = text.slice(0, i); } }; } </script> {#if visible} <p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p> {/if}

延迟函数返回值:多个过渡如何协同

如果过渡函数返回的是函数而不是过渡配置对象,该函数会在下一个 microtask 中被调用。这允许多个过渡相互协调,从而实现 crossfade 这类"延迟过渡"——两个列表的元素需要等 DOM 更新完毕后互相"看见"对方,才能计算位移。

运行时源码印证了这一点:animate()中检测到options是函数时,会queue_micro_task延迟执行,并返回一个同步的 facade(abort/deactivate/reset/t),让调用方无需async/await也能正确中止或查询进度(见 transitions.js)。crossfade正是利用了这一机制。

可逆双向过渡的实现

transition:的"中途平滑反转"能力在源码中由counterpart(对手方动画)实现:intro 与 outro 互相持有引用。当反向过渡开始时,运行时会读取counterpart?.t()获取对手方当前进度t1,以t1为起点、按剩余比例Math.abs(delta)缩放时长重新生成关键帧,于是动画从当前画面无缝反方向进行,而不是从头重放(见 transitions.js 的animation.onfinish回调)。同样地,过渡进行中被重新求值的参数会沿用旧 options(current_options ??= ...),防止因duration等变化导致画面跳变。

过渡事件

带过渡的元素除了标准 DOM 事件外,还会派发四个自定义事件:

  • introstart/introend:入场开始 / 结束
  • outrostart/outroend:离场开始 / 结束

源码中由dispatch_event()通过element.dispatchEvent(new CustomEvent(type))派发,且包在without_reactive_context中,避免事件回调内的状态写入被意外追踪(见 transitions.js)。

{#if visible} <p transition:fly={{ y: 200, duration: 2000 }} onintrostart={() => (status = 'intro started')} onoutrostart={() => (status = 'outro started')} onintroend={() => (status = 'intro ended')} onoutroend={() => (status = 'outro ended')} > Flies in and out </p> {/if}

运行时执行路径小结

将上文源码证据串起来,一次过渡的完整执行路径是:

  1. 编译期TransitionDirectivevisitor 将transition:fade|global={{...}}编译为带标志位的$.transition(flags, element, fn, paramsThunk)调用,并插入到after_update阶段;
  2. 初始化transition()解析direction,创建{ is_global, in, out, stop }管理器挂到当前 effect 的nodes.t上;对 intro,按局部/全局规则决定是否创建播放 effect;
  3. 播放animate()先排队一个 microtask——源码注释解释这是为了让同一批次的嵌套过渡都先测量 DOM 再施加初始样式,且仍在下一帧绘制之前完成;随后创建一个fill: 'forwards'的占位动画覆盖delay期,防止元素在无样式的状态下被绘制;
  4. 关键帧:delay 结束后,按duration / (1000/60)向上取整的帧数逐帧调用css(t, u)生成关键帧数组,再调用element.animate(keyframes, { duration, fill: 'forwards' })
  5. 每帧 tick:若配置含tick,通过loop()在动画running期间按当前currentTime映射出t并回调;
  6. 事件与收尾onfinish触发introend/outroend,清理占位动画与临时样式;abort()则取消动画并置空effect/onfinish,源码注释指明这是为防止 Chromium 内存泄漏及cancel()后误触发onfinish

相关资源

  • 内置过渡实现:transition/index.js,类型定义:transition/public.d.ts
  • 客户端运行时核心:transitions.js
  • 编译器指令处理:TransitionDirective.js
  • prefersReducedMotion实现:motion/index.js
  • 官方文档原文:14-transition.md
  • 测试用例(runes 运行时):tests/runtime-runes/samples,其中包含dynamic-transitionasync-transition-blockerstransition-component等与过渡相关的样本目录,可用于验证不同使用场景下的行为

【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 9:46:20

从能跑到全能:腾讯云AI Skills实战与Agent工程化部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:42:54

深度学习文本摘要生成毕设全攻略:从数据处理到模型微调

简介&#xff1a;一套基于深度学习的文本摘要自动生成毕业设计实现&#xff0c;聚焦Transformer模型在长文档摘要任务中的应用&#xff0c;面向自然语言处理方向的本科毕业生&#xff0c;帮助掌握从数据预处理到模型训练、评估的完整流程。资源包共34个文件&#xff0c;以Pytho…

作者头像 李华
网站建设 2026/9/7 9:41:34

OpenAI安全团队变动对AI安全对齐与API开发的影响分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:40:51

投票数据分析系统:从数据采集到实时可视化的完整实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:40:31

猫抓:免费的浏览器资源嗅探扩展,页面里的视频直接存下来

猫抓&#xff1a;免费的浏览器资源嗅探扩展&#xff0c;页面里的视频直接存下来 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓&#xff08;c…

作者头像 李华
网站建设 2026/9/7 9:40:00

声音故事时间线证据矩阵工具:从输入校验到离线报告的完整实现

声音故事时间线证据矩阵工具&#xff1a;从输入校验到离线报告的完整实现 项目编号&#xff1a;20260906-010。本文代码、测试、文档、示例数据和效果图均为独立编写&#xff0c;不包含热点产品或开源项目源码、品牌素材与官方截图。 问题与目标 围绕“登记录音来源、人物、事…

作者头像 李华