swiftui-motion

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SwiftUI Motion

SwiftUI 动效

SwiftUI animation core. Loaded for any SwiftUI project (iOS, macOS, multi-target Apple). Concise rules here. Deep-dive in
references/
. Pair with
../motion-principles/SKILL.md
(foundation) and
../mobile-principles/SKILL.md
(touch UX).

SwiftUI 动画核心组件。适用于所有 SwiftUI 项目(iOS、macOS、多目标苹果平台)。 此处为简明规则,深入内容请查看
references/
目录。 建议搭配
../motion-principles/SKILL.md
(基础原理)和
../mobile-principles/SKILL.md
(触控用户体验)阅读。

Animation API decision tree

动画API选择决策树

NeedAPI
Single value over time
withAnimation { } + @State
or
.animation(_, value:)
Multiple coordinated states
PhaseAnimator(phases)
(iOS 17+)
Time-based keyframes
KeyframeAnimator(initialValue:repeating:content:)
(iOS 17+)
Custom property animations
@Animatable
macro (iOS 26+) or
Animatable
protocol (iOS 13+)
Shared element transitions
matchedGeometryEffect(id:in:)
Gesture-driven
DragGesture
/
MagnifyGesture
+
.offset
/
.scaleEffect
Loop forever
.animation(.linear.repeatForever(autoreverses: true), value: ...)
or
.phaseAnimator
Rule: start with
withAnimation
. Reach for
PhaseAnimator
only when you have 3+ ordered states. Reach for
KeyframeAnimator
only when you need parallel time-based tracks.

需求API
随时间变化的单个值
withAnimation { } + @State
.animation(_, value:)
多个协同状态
PhaseAnimator(phases)
(iOS 17+)
基于时间的关键帧
KeyframeAnimator(initialValue:repeating:content:)
(iOS 17+)
自定义属性动画
@Animatable
宏(iOS 26+)或
Animatable
协议(iOS 13+)
共享元素转场
matchedGeometryEffect(id:in:)
手势驱动
DragGesture
/
MagnifyGesture
+
.offset
/
.scaleEffect
无限循环
.animation(.linear.repeatForever(autoreverses: true), value: ...)
.phaseAnimator
规则:
withAnimation
开始。仅当存在3个及以上有序状态时,再使用
PhaseAnimator
。仅当需要并行的时间轨道时,再使用
KeyframeAnimator

Springs (the only easing you should care about)

弹簧动画(你唯一需要关注的缓动效果)

SwiftUI ships 4 named springs (iOS 17+). Use them. Tune
response
/
dampingFraction
only when a preset is wrong.
Preset (iOS 17+)EquivalentMood
.snappy
.spring(response: 0.5, dampingFraction: 0.85)
UI snappy
.bouncy
.spring(response: 0.5, dampingFraction: 0.7)
playful
.smooth
.spring(response: 0.5, dampingFraction: 1.0)
calm, no bounce
.interactiveSpring()
.spring(response: 0.15, dampingFraction: 0.86)
gesture follow
response
is the time the spring takes to settle (lower = snappier, higher = softer).
dampingFraction
is the overshoot intensity in
0...1
(1 = no overshoot, 0 = perpetual oscillation - never use 0). For UI work, stay in
response: 0.2...0.5
and
dampingFraction: 0.7...1.0
. Deep-dive:
references/springs-cheatsheet.md
.
iOS 17+ also exposes
.spring(duration:bounce:)
where
bounce
is
0...1
(0 = critically damped, 1 = full bounce). It's the same spring, exposed in a more designer-friendly way:
swift
.animation(.spring(duration: 0.4, bounce: 0.3), value: state)

SwiftUI 内置了4种命名弹簧动画(iOS 17+)。直接使用即可。仅当预设效果不合适时,再调整
response
/
dampingFraction
参数。
预设(iOS 17+)等效参数风格
.snappy
.spring(response: 0.5, dampingFraction: 0.85)
UI 轻快响应
.bouncy
.spring(response: 0.5, dampingFraction: 0.7)
活泼有趣
.smooth
.spring(response: 0.5, dampingFraction: 1.0)
平稳无弹跳
.interactiveSpring()
.spring(response: 0.15, dampingFraction: 0.86)
手势跟随
response
是弹簧动画稳定所需的时间(值越小越轻快,值越大越柔和)。
dampingFraction
是过冲强度,取值范围为
0...1
(1表示无过冲,0表示持续振荡——切勿使用0)。对于UI开发,建议将参数保持在
response: 0.2...0.5
dampingFraction: 0.7...1.0
范围内。深入内容请查看:
references/springs-cheatsheet.md
iOS 17+ 还提供了
.spring(duration:bounce:)
方法,其中
bounce
取值范围为
0...1
(0表示临界阻尼,1表示完全弹跳)。这是同一种弹簧动画,以更适合设计师的方式呈现:
swift
.animation(.spring(duration: 0.4, bounce: 0.3), value: state)

Implicit vs explicit animations

隐式动画 vs 显式动画

swift
// Implicit - via .animation modifier (binds to a value)
Circle()
    .scaleEffect(scale)
    .animation(.spring(.snappy), value: scale)
swift
// Explicit - via withAnimation block
Button("Grow") {
    withAnimation(.smooth) { scale = 1.5 }
}
Rule: prefer explicit (
withAnimation
) for state changes triggered by user actions; use implicit when any change to a value should always animate (e.g., a progress bar that updates from anywhere). Never both on the same property - the outer
withAnimation
wins, but the implicit
.animation
modifier still runs and stacks confusingly.

swift
// 隐式动画 - 通过 .animation 修饰符(绑定到某个值)
Circle()
    .scaleEffect(scale)
    .animation(.spring(.snappy), value: scale)
swift
// 显式动画 - 通过 withAnimation 代码块
Button("放大") {
    withAnimation(.smooth) { scale = 1.5 }
}
规则: 对于用户操作触发的状态变化,优先使用显式动画(
withAnimation
);当某个值的任何变化都应始终触发动画时(例如,从任意位置更新的进度条),使用隐式动画。切勿对同一属性同时使用两种方式——外层的
withAnimation
会生效,但隐式的
.animation
修饰符仍会运行,导致混乱的叠加效果。

Transitions

转场动画

Transitions drive insertion / removal of views inside an
if
,
switch
, or
ForEach
. They run when the parent's animation context fires (so wrap state mutations in
withAnimation
).
swift
if visible {
    Card().transition(.asymmetric(
        insertion: .move(edge: .bottom).combined(with: .opacity),
        removal: .opacity.animation(.easeIn(duration: 0.15))
    ))
}
BAD - vanish into a black hole:
swift
Card().transition(.scale)  // scales to 0, the universal "broken" feel
GOOD - never scale to 0:
swift
Card().transition(
    .scale(scale: 0.95).combined(with: .opacity)
)
iOS 17+ also has the
.transition(_:)
modifier with custom transitions via the
Transition
protocol - useful for shared timing across many views. For 90% of work, the built-in combinators (
.move
,
.opacity
,
.scale
,
.slide
,
.push
,
.asymmetric
,
.combined(with:)
) are enough.

转场动画用于驱动
if
switch
ForEach
内部视图的插入/移除。当父视图的动画上下文触发时(因此需将状态变更包裹在
withAnimation
中),转场动画会运行。
swift
if visible {
    Card().transition(.asymmetric(
        insertion: .move(edge: .bottom).combined(with: .opacity),
        removal: .opacity.animation(.easeIn(duration: 0.15))
    ))
}
错误示例 - 消失进黑洞:
swift
Card().transition(.scale)  // 缩放到0,是普遍的"破碎感"效果
正确示例 - 切勿缩放到0:
swift
Card().transition(
    .scale(scale: 0.95).combined(with: .opacity)
)
iOS 17+ 还提供了
.transition(_:)
修饰符,可通过
Transition
协议实现自定义转场——适用于多视图共享时序的场景。对于90%的开发工作,内置的组合器(
.move
.opacity
.scale
.slide
.push
.asymmetric
.combined(with:)
) 已足够使用。

matchedGeometryEffect (hero animations)

matchedGeometryEffect(英雄动画)

Tag two views with the same
id
in the same
Namespace
. SwiftUI interpolates frame and position when the source view is replaced.
swift
struct Gallery: View {
    @Namespace private var ns
    @State private var expanded = false

    var body: some View {
        ZStack {
            if expanded {
                LargeCard()
                    .matchedGeometryEffect(id: "card", in: ns)
                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = false } }
            } else {
                SmallCard()
                    .matchedGeometryEffect(id: "card", in: ns)
                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = true } }
            }
        }
    }
}
isSource: true
(default on the source-of-truth view) tells SwiftUI which frame to interpolate from. Common gotchas: id collisions across unrelated namespaces, view identity instability (use stable ids, not array indices), and animating out of an
if
branch where the destination view doesn't exist yet (wrap both branches inside the same parent, use opacity to hide instead of removing).

在同一个
Namespace
中,为两个视图标记相同的
id
。当源视图被替换时,SwiftUI 会自动插值过渡帧和位置。
swift
struct Gallery: View {
    @Namespace private var ns
    @State private var expanded = false

    var body: some View {
        ZStack {
            if expanded {
                LargeCard()
                    .matchedGeometryEffect(id: "card", in: ns)
                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = false } }
            } else {
                SmallCard()
                    .matchedGeometryEffect(id: "card", in: ns)
                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = true } }
            }
        }
    }
}
isSource: true
(默认在源视图上)用于告知SwiftUI从哪个帧开始插值。常见陷阱:无关命名空间的id冲突、视图标识不稳定(使用稳定id,而非数组索引)、在目标视图尚未存在的
if
分支中触发动画(将两个分支包裹在同一个父视图中,使用透明度隐藏而非移除视图)。

PhaseAnimator (iOS 17+)

PhaseAnimator(iOS 17+)

For ordered state choreography. Define a
CaseIterable + Hashable
enum, SwiftUI walks through phases sequentially, settling on the last one.
swift
enum SuccessPhase: CaseIterable { case start, scaleUp, rotate, settle }

struct SuccessCheck: View {
    @State private var trigger = false

    var body: some View {
        Image(systemName: "checkmark.circle.fill")
            .font(.system(size: 64))
            .foregroundStyle(.green)
            .phaseAnimator(SuccessPhase.allCases, trigger: trigger) { view, phase in
                view
                    .scaleEffect(phase == .start ? 0 : phase == .settle ? 1 : 1.2)
                    .rotationEffect(.degrees(phase == .rotate ? 360 : 0))
                    .opacity(phase == .start ? 0 : 1)
            } animation: { phase in
                switch phase {
                case .start: .smooth(duration: 0.05)
                case .scaleUp: .spring(.bouncy, blendDuration: 0.25)
                case .rotate: .spring(response: 0.4, dampingFraction: 0.8)
                case .settle: .smooth(duration: 0.2)
                }
            }
            .onTapGesture { trigger.toggle() }
    }
}
trigger:
is optional - omit it to advance through phases automatically once on appear. Use it when you need an external signal (button tap, model update). Phases run sequentially, never in parallel - if you need parallelism, use
KeyframeAnimator
.

用于有序状态编排。定义一个
CaseIterable + Hashable
枚举,SwiftUI 会按顺序遍历各个阶段,最终停留在最后一个阶段。
swift
enum SuccessPhase: CaseIterable { case start, scaleUp, rotate, settle }

struct SuccessCheck: View {
    @State private var trigger = false

    var body: some View {
        Image(systemName: "checkmark.circle.fill")
            .font(.system(size: 64))
            .foregroundStyle(.green)
            .phaseAnimator(SuccessPhase.allCases, trigger: trigger) { view, phase in
                view
                    .scaleEffect(phase == .start ? 0 : phase == .settle ? 1 : 1.2)
                    .rotationEffect(.degrees(phase == .rotate ? 360 : 0))
                    .opacity(phase == .start ? 0 : 1)
            } animation: { phase in
                switch phase {
                case .start: .smooth(duration: 0.05)
                case .scaleUp: .spring(.bouncy, blendDuration: 0.25)
                case .rotate: .spring(response: 0.4, dampingFraction: 0.8)
                case .settle: .smooth(duration: 0.2)
                }
            }
            .onTapGesture { trigger.toggle() }
    }
}
trigger:
是可选参数——省略该参数时,视图出现后会自动依次执行各个阶段。当需要外部信号(按钮点击、模型更新)触发时,使用该参数。各个阶段按顺序执行,不会并行——如果需要并行效果,请使用
KeyframeAnimator

KeyframeAnimator (iOS 17+)

KeyframeAnimator(iOS 17+)

For continuous, time-based animations with parallel tracks. Each
KeyframeTrack
animates one keypath independently; SwiftUI runs them all together.
swift
struct AnimationValues {
    var scale: Double = 1
    var rotation: Angle = .zero
    var opacity: Double = 1
}

struct HeartTap: View {
    @State private var counter = 0

    var body: some View {
        Image(systemName: "heart.fill")
            .font(.system(size: 64))
            .foregroundStyle(.pink)
            .keyframeAnimator(initialValue: AnimationValues(), trigger: counter) { content, values in
                content
                    .scaleEffect(values.scale)
                    .rotationEffect(values.rotation)
                    .opacity(values.opacity)
            } keyframes: { _ in
                KeyframeTrack(\.scale) {
                    SpringKeyframe(1.3, duration: 0.15)
                    SpringKeyframe(1.0, duration: 0.3, spring: .bouncy)
                }
                KeyframeTrack(\.rotation) {
                    CubicKeyframe(.degrees(15), duration: 0.1)
                    CubicKeyframe(.degrees(-15), duration: 0.2)
                    CubicKeyframe(.degrees(0), duration: 0.15)
                }
            }
            .onTapGesture { counter += 1 }
    }
}
Four keyframe types:
LinearKeyframe
(constant velocity between points),
SpringKeyframe
(settles with spring),
CubicKeyframe
(cubic bezier ease),
MoveKeyframe
(jump cut, no interpolation). Trigger on a value change to re-run the animation. Deep-dive:
references/phase-keyframe-deep.md
.

用于连续的、基于时间的并行轨道动画。每个
KeyframeTrack
独立动画一个关键路径;SwiftUI 会同时运行所有轨道。
swift
struct AnimationValues {
    var scale: Double = 1
    var rotation: Angle = .zero
    var opacity: Double = 1
}

struct HeartTap: View {
    @State private var counter = 0

    var body: some View {
        Image(systemName: "heart.fill")
            .font(.system(size: 64))
            .foregroundStyle(.pink)
            .keyframeAnimator(initialValue: AnimationValues(), trigger: counter) { content, values in
                content
                    .scaleEffect(values.scale)
                    .rotationEffect(values.rotation)
                    .opacity(values.opacity)
            } keyframes: { _ in
                KeyframeTrack(\.scale) {
                    SpringKeyframe(1.3, duration: 0.15)
                    SpringKeyframe(1.0, duration: 0.3, spring: .bouncy)
                }
                KeyframeTrack(\.rotation) {
                    CubicKeyframe(.degrees(15), duration: 0.1)
                    CubicKeyframe(.degrees(-15), duration: 0.2)
                    CubicKeyframe(.degrees(0), duration: 0.15)
                }
            }
            .onTapGesture { counter += 1 }
    }
}
四种关键帧类型:
LinearKeyframe
(两点间匀速)、
SpringKeyframe
(弹簧稳定效果)、
CubicKeyframe
(三次贝塞尔缓动)、
MoveKeyframe
(跳切,无插值)。当值变化时触发,重新运行动画。深入内容请查看:
references/phase-keyframe-deep.md

Animatable / @Animatable

Animatable / @Animatable

For custom drawing that needs interpolation. The
@Animatable
macro (iOS 26+, WWDC25) auto-synthesizes
animatableData
for any
Equatable
properties; the older
Animatable
protocol (iOS 13+) still works pre-26.
swift
struct ProgressRing: Shape {
    var progress: Double  // 0...1

    var animatableData: Double {
        get { progress }
        set { progress = newValue }
    }

    func path(in rect: CGRect) -> Path {
        var p = Path()
        p.addArc(
            center: CGPoint(x: rect.midX, y: rect.midY),
            radius: rect.width / 2,
            startAngle: .degrees(-90),
            endAngle: .degrees(-90 + 360 * progress),
            clockwise: false
        )
        return p
    }
}

ProgressRing(progress: progress)
    .stroke(.tint, lineWidth: 6)
    .animation(.spring(.smooth), value: progress)
For multi-property shapes use
AnimatablePair<A, B>
(or nested pairs) as
animatableData
. The
@Animatable
macro removes that boilerplate when properties are
Equatable + Animatable
.

用于需要插值的自定义绘制。
@Animatable
宏(iOS 26+,WWDC25)会自动为任何
Equatable
属性合成
animatableData
;旧版的
Animatable
协议(iOS 13+)在iOS 26之前仍可使用。
swift
struct ProgressRing: Shape {
    var progress: Double  // 0...1

    var animatableData: Double {
        get { progress }
        set { progress = newValue }
    }

    func path(in rect: CGRect) -> Path {
        var p = Path()
        p.addArc(
            center: CGPoint(x: rect.midX, y: rect.midY),
            radius: rect.width / 2,
            startAngle: .degrees(-90),
            endAngle: .degrees(-90 + 360 * progress),
            clockwise: false
        )
        return p
    }
}

ProgressRing(progress: progress)
    .stroke(.tint, lineWidth: 6)
    .animation(.spring(.smooth), value: progress)
对于多属性形状,使用
AnimatablePair<A, B>
(或嵌套配对)作为
animatableData
。当属性为
Equatable + Animatable
时,
@Animatable
宏可消除这些样板代码。

Gestures

手势

GestureTypeUse
TapGesture
discretetap, double-tap (
count: 2
)
LongPressGesture
discrete + continuous (
onEnded
/
onChanged
)
context menus, hold-to-record
DragGesture
continuousdrag, swipe-to-dismiss
MagnifyGesture
continuouspinch-zoom (iOS 17+, replaces
MagnificationGesture
)
RotateGesture
continuousrotate (iOS 17+, replaces
RotationGesture
)
SpatialTapGesture
discretetap with location info
swift
let tap = TapGesture().onEnded { print("tap") }
let drag = DragGesture().onChanged { value in offset = value.translation }
ZStack { ... }
    .gesture(tap.simultaneously(with: drag))
Three composition operators:
.simultaneously(with:)
(parallel recognition),
.sequenced(before:)
(one must complete first),
.exclusively(before:)
(one or the other, not both). For fine-grained control over when child views can claim the gesture, use
.simultaneousGesture(_, including: GestureMask)
with
.gesture
,
.subviews
,
.all
, or
.none
. Deep-dive:
references/gestures-swiftui.md
.

手势类型用途
TapGesture
离散型点击、双击(
count: 2
LongPressGesture
离散型+连续型(
onEnded
/
onChanged
上下文菜单、按住录制
DragGesture
连续型拖拽、滑动关闭
MagnifyGesture
连续型捏合缩放(iOS 17+,替代
MagnificationGesture
RotateGesture
连续型旋转(iOS 17+,替代
RotationGesture
SpatialTapGesture
离散型带位置信息的点击
swift
let tap = TapGesture().onEnded { print("点击") }
let drag = DragGesture().onChanged { value in offset = value.translation }
ZStack { ... }
    .gesture(tap.simultaneously(with: drag))
三种组合运算符:
.simultaneously(with:)
(并行识别)、
.sequenced(before:)
(必须先完成一个)、
.exclusively(before:)
(二选一,不可同时)。如需精细控制子视图何时可捕获手势,可使用
.simultaneousGesture(_, including: GestureMask)
,搭配
.gesture
.subviews
.all
.none
参数。深入内容请查看:
references/gestures-swiftui.md

Anti-Patterns (BAD / GOOD)

反模式(错误示例 / 正确示例)

1. Deprecated
.animation
form (no value binding)

1. 已弃用的
.animation
形式(无值绑定)

swift
// BAD - implicit-anim-everywhere, deprecated in iOS 15+
Circle().scaleEffect(scale).animation(.easeInOut)
swift
// GOOD - bind to a specific value
Circle().scaleEffect(scale).animation(.easeInOut, value: scale)
// OR
withAnimation(.easeInOut) { scale = 1.5 }
swift
// 错误示例 - 全局隐式动画,iOS 15+ 已弃用
Circle().scaleEffect(scale).animation(.easeInOut)
swift
// 正确示例 - 绑定到特定值
Circle().scaleEffect(scale).animation(.easeInOut, value: scale)
// 或
withAnimation(.easeInOut) { scale = 1.5 }

2. Animating frame size directly

2. 直接动画帧大小

swift
// BAD - .frame() drives layout pass every frame, drops fps under load
Card().frame(height: expanded ? 400 : 100)
    .animation(.spring(), value: expanded)
swift
// GOOD - animate transform-equivalents (scale, offset) that the compositor handles
Card()
    .frame(height: 400)
    .scaleEffect(expanded ? 1 : 0.4, anchor: .top)
    .animation(.spring(), value: expanded)
// OR for actual layout transitions, use matchedGeometryEffect
swift
// 错误示例 - .frame() 每帧都会触发布局计算,负载下会掉帧
Card().frame(height: expanded ? 400 : 100)
    .animation(.spring(), value: expanded)
swift
// 正确示例 - 动画 compositor 处理的变换等效属性(缩放、偏移)
Card()
    .frame(height: 400)
    .scaleEffect(expanded ? 1 : 0.4, anchor: .top)
    .animation(.spring(), value: expanded)
// 如需实际布局转场,使用 matchedGeometryEffect

3. Scale to 0 (the vanish-into-nothing trap)

3. 缩放到0(消失陷阱)

swift
// BAD - element vanishes into a black hole, feels broken
Card().transition(.scale)
swift
// GOOD - minimum scale 0.9-0.95 + opacity
Card().transition(.scale(scale: 0.95).combined(with: .opacity))
swift
// 错误示例 - 元素消失进黑洞,体验糟糕
Card().transition(.scale)
swift
// 正确示例 - 最小缩放0.9-0.95 + 透明度
Card().transition(.scale(scale: 0.95).combined(with: .opacity))

4.
withAnimation
inside
body

4. 在
body
中使用
withAnimation

swift
// BAD - body runs on every render, animation re-fires arbitrarily
var body: some View {
    let _ = withAnimation(.spring()) { scale = 1.2 }  // never do this
    Circle().scaleEffect(scale)
}
swift
// GOOD - trigger from user actions or .onChange
var body: some View {
    Circle()
        .scaleEffect(scale)
        .onTapGesture {
            withAnimation(.spring()) { scale = scale == 1 ? 1.2 : 1 }
        }
}

swift
// 错误示例 - body 每次渲染都会运行,动画会任意重新触发
var body: some View {
    let _ = withAnimation(.spring()) { scale = 1.2 }  // 切勿这样做
    Circle().scaleEffect(scale)
}
swift
// 正确示例 - 从用户操作或 .onChange 触发
var body: some View {
    Circle()
        .scaleEffect(scale)
        .onTapGesture {
            withAnimation(.spring()) { scale = scale == 1 ? 1.2 : 1 }
        }
}

Reduced motion respect

尊重减少动效设置

Mandatory. SwiftUI exposes the iOS / macOS "Reduce Motion" accessibility setting via the environment. See
../motion-principles/SKILL.md
for the cross-platform rationale.
swift
struct Hero: View {
    @Environment(\.accessibilityReduceMotion) var reduceMotion
    @State private var shown = false

    var body: some View {
        Text("Welcome")
            .opacity(shown ? 1 : 0)
            .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))
            .animation(reduceMotion ? .none : .spring(.smooth), value: shown)
            .onAppear { shown = true }
    }
}
Rule: cross-fades and opacity are still allowed under reduced motion; large translations, scale-from-zero, parallax, and looping motion must be neutralized.

这是必须遵守的要求。SwiftUI 通过环境变量暴露 iOS / macOS 的"减少动效"无障碍设置。跨平台原理请查看
../motion-principles/SKILL.md
swift
struct Hero: View {
    @Environment(\.accessibilityReduceMotion) var reduceMotion
    @State private var shown = false

    var body: some View {
        Text("欢迎")
            .opacity(shown ? 1 : 0)
            .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))
            .animation(reduceMotion ? .none : .spring(.smooth), value: shown)
            .onAppear { shown = true }
    }
}
规则:减少动效模式下仍允许使用淡入淡出和透明度动画;必须禁用大型位移、从0缩放、视差和循环动画。

Quick Reference: Loading sub-skills

快速参考:加载子技能

NeedLoad
Springs deep-dive (response/dampingFraction tuning, mood)
references/springs-cheatsheet.md
PhaseAnimator + KeyframeAnimator complex sequences
references/phase-keyframe-deep.md
Gesture composition + conflict resolution
references/gestures-swiftui.md
Advanced visuals (Metal, Liquid Glass)
../swiftui-graphics/SKILL.md
Cross-platform UX (mobile, desktop)
../mobile-principles/SKILL.md
,
../desktop-principles/SKILL.md
Foundation (timing, easing, a11y)
../motion-principles/SKILL.md

需求加载路径
弹簧动画深入(response/dampingFraction 调优、风格)
references/springs-cheatsheet.md
PhaseAnimator + KeyframeAnimator 复杂序列
references/phase-keyframe-deep.md
手势组合 + 冲突解决
references/gestures-swiftui.md
高级视觉效果(Metal、液态玻璃)
../swiftui-graphics/SKILL.md
跨平台用户体验(移动、桌面)
../mobile-principles/SKILL.md
../desktop-principles/SKILL.md
基础原理(时序、缓动、无障碍)
../motion-principles/SKILL.md

Sources

资料来源