动效指南
用动效解释,而非干扰
这是一份实用指南,帮助判断何时应当产生位移、选择合适的令牌,并交付快速、一致且可访问的动效。
判断框架
动起来之前的四个问题
最好的动效决定,往往发生在设置时长或弹簧参数之前。
先说明变化
指出用户刚刚操作的对象,以及操作后需要看见的位置或状态。
用连续的位置关系帮助理解,避免无关元素一起移动。
控制等待
高频控件立即响应。弹层可以解释来源,但不应阻塞用户继续输入。
反复切换时也要支持中断,不能等待上一段动画播完。
保持物理连续
同一个对象移动或变形时,共享布局和弹簧用于衔接当前位置与新目标。
进度条使用线性时间;手势从释放时的位置和速度继续。
提供静态路径
减少动态效果时仍需显示状态、焦点和操作结果,移除装饰性的位移与循环。
不要让可访问性设置改变功能或键盘操作。
动效令牌
处处使用同一种语言
orvui 将有意图的动效保存在共享令牌中。按用途选择,让组件彼此协调,而非以完全相同的方式移动。
对比曲线
相同距离、相同时长,不同用途。
进入和退出立即响应,再平稳结束。
屏幕上已有的对象自然地加速与减速。
SPRING_PRESS
为按钮和其他可按压表面提供快速、有分量的反馈。
SPRING_LAYOUT
用于需要持续空间运动的共享表面与指示器。
文档中的切换
内容变化,阅读位置不变
页面导航与局部视图使用不同节奏。切换下面的章节,观察正文变化与稳定的导航框架。
安装
添加组件、加载样式,再连接应用状态。
pnpm add @orvui/vue- 页面切换120ms / 220ms
- 只切换阅读区域,保留导航框架。旧正文淡出,新正文轻移 8px 后落稳;快速切换以最后一次选择为准。
- 预览、用法与源码180ms
- 保持操作栏位置稳定,在当前示例内切换视图。隐藏内容不能接收键盘焦点,也不重新播放整页入场。
- 锚点、查询与减少动态效果0ms / 80ms
- 同页锚点和查询更新不触发整页转场;减少动态效果时只保留 80ms 淡入,取消位移。首次 SSR 与水合直接显示正文。
export default defineNuxtConfig({
experimental: { viewTransition: true },
})<template>
<!-- Query and anchor changes keep the existing page. -->
<NuxtPage :page-key="route => route.path" />
</template>时长
快到足以感觉即时
时长取决于大小、距离和频率。这些范围是起点,不是必须机械达到的指标。
界面动效默认控制在 300ms 以内。更长的动效适合解释性演示、有意图的手势和大范围空间变化。
配方
可以复制的模式
每个配方都把目的与可用于生产的令牌、减少动态效果状态和具体失败模式联系起来。
按压反馈
让可按压表面在指针按下时轻微收缩,松开后回到原位。
查看配方+
<motion.button :while-press="reduce ? undefined : { scale: 0.97 }" :transition="SPRING_PRESS">Continue</motion.button>图标响应
按操作含义选择短暂运动,并让键盘聚焦获得同等反馈。
查看配方+
<motion.span :animate="active && !reduce ? { rotate: [0, 10, -6, 0] } : { rotate: 0 }" :transition="{ duration: 0.24 }"><Bell /></motion.span>新内容出现
先让整体表面出现,再呈现内容,避免读者追逐多个运动目标。
新表面
在获得焦点前就绪。
查看配方+
<motion.section :initial="{ opacity: 0, y: reduce ? 0 : 8 }" :animate="{ opacity: 1, y: 0 }" :transition="{ duration: 0.2 }"><slot /></motion.section>表面展开
触发器与展开区域属于同一个对象时,用共享布局保留这条联系。
查看配方+
<motion.div layout :transition="CONTINUITY_SPRING"><button @click="open = !open">Toggle</button><div v-if="open">Panel content</div></motion.div>视图切换
保持外层布局稳定,在内部用短距离和透明度切换两种视图。
查看配方+
<div class="grid rounded-xl bg-background p-4"><motion.div v-for="current in views" :key="current" class="col-start-1 row-start-1" :aria-hidden="current !== view" :inert="current !== view" :initial="false" :animate="{ opacity: current === view ? 1 : 0 }" :transition="{ duration: reduce ? 0.08 : 0.18 }"><slot :view="current" /></motion.div></div>可访问性
减少动态效果是一种设计状态
不要移除全部过渡。保留帮助理解的反馈,移除可能引起不适的运动。
保留含义,移除位移。
保留透明度、颜色和即时状态变化。去掉视差、大幅变换、反复缩放和弹簧过冲。
const reduce = useReducedMotion();
const start = computed(() => ({ opacity: 0, y: reduce.value ? 0 : 8 }));
const target = { opacity: 1, y: 0 };