Svelte 单向转场实战:in: 与 out: 指令的用法、行为差异与底层实现 Svelte 单向转场实战in: 与 out: 指令的用法、行为差异与底层实现【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本篇围绕 Svelte 模板语法中的in:与out:转场指令展开先讲清它们与双向transition:指令在“反转/重播”语义上的本质区别再结合 Svelte 编译器对指令的处理流程和客户端运行时$.transition的实现解释为什么单向转场在中途被打断时会“从头再来”帮助你在做列表、面板、弹窗等需要非对称进出动效的场景时做出正确的指令选择。in:和out:与transition:的核心区别in:和out:指令与transition:指令的使用方式完全相同唯一的区别在于它们产生的转场不是双向bidirectional的。具体表现为两条规则如果元素在in 转场尚未结束时所在的块被移除触发 outroin 转场不会反转而是继续播放完毕与 out 转场同时进行如果一个out 转场被打断比如元素又重新插回 DOM或者同一元素的 outro 被取消/重新触发转场从头开始restart from scratch而不是从当前进度平滑续播。作为对照双向的transition:指令可以在转场进行中被平滑反转——运行时会让新动画从对方的当前位置counterpart.t()起步而不是跳变到新起点。这一点在源码中体现得非常直白见下文“运行时实现”一节。基本用法下面是文档给出的标准示例切换visible状态时div从下方 200px 处飞入fly退出时则做淡出fade——进与出使用两个不同的转场函数script import { fade, fly } from svelte/transition; let visible $state(false); /script label input typecheckbox bind:checked{visible} visible /label {#if visible} div in:fly{{ y: 200 }} out:fadeflies in, fades out/div {/if}要点说明参数部分的双花括号{{ ... }}不是特殊语法它只是表达式标签里的一个对象字面量例如in:fly{{ y: 200 }}等价于传入{ y: 200 }内置转场函数fade、fly、blur、draw、flip、slide等从svelte/transition模块导入参考 svelte/transition 模块说明参数对象通常包含delay、duration、easing等字段由各内置转场自行定义默认值。单向语义在交互中意味着什么transition:与in:/out:的行为差异在实际交互中非常直观快速连续切换使用transition:fly时元素飞出到一半再切回来会从当前偏移量平滑滑回使用in:fly时元素重新插入后fly会重新从y: 200处完整飞入而不是接上次未完成的进度。in 过程中立即 outroin 转场继续播完例如继续滑到位同时 out 转场并行启动两者叠加呈现这是文档明确描述的行为——“anintransition will continue to play alongside theouttransition, rather than reversing”。out 被打断out 转场直接作废并从零重启没有“反转续播”的优化路径。这种语义适合进出动效本就不对称的场景如“弹入 淡出”“滑入 原位收缩”也适合希望“打断即重来”的干脆手感。Local 与 Global 修饰符in:和out:同样支持|global修饰符语义与transition:一致参见 transition 文档{#if x} {#if y} !-- 默认 local仅在 y 变化时播放 -- p in:fade out:fadeonly plays when y changes/p !-- globalx 或 y 变化都会触发 -- p in:fade|global out:fade|globalplays when x or y change/p {/if} {/if}默认情况下转场是local的只有元素所在的那个块自身被创建/销毁时才播放父块变化不会带动它。需要跨块级联时才加|global。转场事件与无障碍带转场的元素会在标准 DOM 事件之外派发四个事件事件触发时机introstartin 转场开始introendin 转场完成outrostartout 转场开始outroendout 转场完成{#if visible} p in:fly{{ y: 200, duration: 2000 }} out:fade{{ duration: 2000 }} onintrostart{() (status intro started)} onoutrostart{() (status outro started)} onintroend{() (status intro ended)} onoutroend{() (status outro ended)} Flies in and out /p {/if}另外转场由 Web Animations API 驱动而非 CSS transition因此全局media (prefers-reduced-motion: reduce)规则把transition-duration/animation-duration归零的做法对转场无效。需要为偏好减少动态效果的设备降级动效时应使用prefersReducedMotion。源码解析指令如何变成单向转场编译阶段指令被压缩为标志位在 TransitionDirective.js 中编译器把in:/out:/transition:三种指令统一转换为一次$.transition(flags, element, get_fn, get_params)调用其中flags是一组位标志定义于 constants.jsTRANSITION_IN 1由in:或transition:置位node.introTRANSITION_OUT 1 1由out:或transition:置位node.outroTRANSITION_GLOBAL由|global修饰符置位。也就是说transition:fade编译后flags IN | OUTin:fade只有INout:fade只有OUT。方向语义在运行时由这些标志推出。运行时direction 的推导transitions.js 中的transition(flags, element, get_fn, get_params)负责创建“转场管理器”并挂到当前 effect 上var is_intro (flags TRANSITION_IN) ! 0; var is_outro (flags TRANSITION_OUT) ! 0; var is_both is_intro is_outro; var direction is_both ? both : is_intro ? in : out;directionin | out | both会作为第三个参数options传给你的自定义转场函数因此自定义转场可以根据方向返回不同的时长或曲线——in:fly和out:fly拿到的是同一个函数但options.direction不同。单向转场为何“打断即重来”管理器暴露了in()与out(fn)两个方法transitions.js#L234-L290in() { element.inert inert; if (!is_intro) { outro?.abort(); outro?.reset?.(); return; } if (!is_outro) { // if we intro then outro then intro again, we want to abort the first intro, // if its not a bidirectional transition intro?.abort(); } intro animate(element, get_options(), outro, 1, ...) }注意这段注释与逻辑当元素只声明了in:is_outro为 false时再次 intro 会直接intro?.abort()旧动画——旧的 in 转场进度被丢弃新转场从t0重新播放。这正是文档所说“out 被打断则从头开始、in 不反转”的运行时来源。而双向transition:is_outro为 true时不会走abort分支而是把上一次的动画作为counterpart传给animate。在 transitions.js#L423-L429// for bidirectional transitions, we start from the current position, // rather than doing a full intro/outro var t1 counterpart?.t() ?? 1 - t2; var delta t2 - t1; var duration options.duration * Math.abs(delta);即新动画从旧动画当前进度t1起步时长按剩余距离|delta|等比缩短——这就是transition:能“平滑反转”的机制也是in:/out:刻意不做的事情。此外还有两个与单向/双向都相关的实现细节inert状态out 转场启动时element.inert truetransitions.js#L275转场进行中元素不再响应交互in 开始时恢复为挂载前的原值。测试样例 if-transition-inert 专门验证了in:fade/out:fade包裹的元素在嵌套块变化时保持惯性inert行为事件派发introstart/introend/outrostart/outroend通过dispatch_event以CustomEvent形式派发transitions.js#L17-L21且在无响应式上下文中派发避免在事件处理器中读取状态造成副作用。自定义转场函数中的 direction自定义转场函数签名为(node, params, options) {...}其中options.direction对单向指令尤为有用/** * param {HTMLElement} node * param {{ duration?: number }} params * param {{ direction: in | out | both }} options */ function directional(node, { duration 400 }, { direction }) { return { duration: direction out ? duration / 2 : duration, css: (t) opacity: ${direction in ? t : 1 - t} }; }配合in:directional/out:directional使用退出动画可以更短促。返回值中的css函数用于生成 Web Animations 关键帧t在 in 转场中从 0 走到 1out 转场中从 1 走到 0u 1 - t能走css就不要用tick——Web 动画可以脱离主线程运行在低性能设备上更不容易掉帧。返回tick的写法如逐字打印文本会在转场期间每帧回调适合css无法表达的状态变更。小结in:/out:与transition:语法一致、可自由组合同一个元素可各用一个不同函数也支持|global、参数对象、转场事件核心差异是单向性in 转场在 outro 到来时继续播完而非反转out 转场被中断时从零重启编译器把方向压缩成TRANSITION_IN/OUT位标志运行时据此推导direction并决定“abort 重来”还是“按counterpart.t()续播”见 transitions.js 与 TransitionDirective.js需要平滑反转/续播选transition:需要“进 A 出 B”或打断即重来的干脆语义选in:out:。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考