Transition
Transition 组件为元素或组件提供进入/离开的 CSS 过渡动画。它可以配合条件渲染(v-if)、显示隐藏(v-show)、Dynamic、Freeze 等使用。
基本用法
tsx
import { Transition, ref } from 'vitarx'
function App() {
const show = ref(true)
return (
<div>
<button
onClick={() => {
show.value = !show.value
}}
>
{show.value ? '隐藏' : '显示'}
</button>
<Transition>{show.value && <div class="box">我会淡入淡出</div>}</Transition>
</div>
)
}对应的 CSS:
css
/* 进入动画 */
.v-enter-from {
opacity: 0;
transform: translateY(-10px);
}
.v-enter-active {
transition: all 0.3s ease;
}
.v-enter-to {
opacity: 1;
transform: translateY(0);
}
/* 离开动画 */
.v-leave-from {
opacity: 1;
transform: translateY(0);
}
.v-leave-active {
transition: all 0.3s ease;
}
.v-leave-to {
opacity: 0;
transform: translateY(-10px);
}过渡类名
Transition 使用 6 个 CSS 类名来控制过渡动画,默认前缀为 v:
| 类名 | 阶段 | 说明 |
|---|---|---|
v-enter-from | 进入开始 | 元素插入时的初始状态,只持续一帧 |
v-enter-active | 进入过程 | 整个进入动画期间生效,用于定义 transition |
v-enter-to | 进入结束 | 进入动画的最终状态 |
v-leave-from | 离开开始 | 离开动画的初始状态 |
v-leave-active | 离开过程 | 整个离开动画期间生效,用于定义 transition |
v-leave-to | 离开结束 | 离开动画的最终状态 |
动画流程如下:
text
进入:v-enter-from → v-enter-active → v-enter-to
离开:v-leave-from → v-leave-active → v-leave-to自定义过渡类名
name 属性
通过 name 属性可以修改类名前缀,避免样式冲突:
tsx
<Transition name="fade">{show.value && <div>淡入淡出</div>}</Transition>对应的 CSS 类名变为 fade-enter-from、fade-enter-active 等:
css
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}自定义类名属性
你也可以单独指定每个阶段的类名,这在结合第三方动画库(如 Animate.css)时很有用:
tsx
<Transition
enterFromClass="animate__animated animate__fadeIn"
enterActiveClass="animate__animated animate__fadeIn"
enterToClass="animate__animated animate__fadeIn"
leaveFromClass="animate__animated animate__fadeOut"
leaveActiveClass="animate__animated animate__fadeOut"
leaveToClass="animate__animated animate__fadeOut"
>
{show.value && <div>动画效果</div>}
</Transition>可用的自定义类名属性:
| 属性 | 说明 |
|---|---|
enterFromClass | 进入开始状态类名 |
enterActiveClass | 进入过程类名 |
enterToClass | 进入结束状态类名 |
leaveFromClass | 离开开始状态类名 |
leaveActiveClass | 离开过程类名 |
leaveToClass | 离开结束状态类名 |
appearFromClass | 首次出现开始状态类名 |
appearActiveClass | 首次出现过程类名 |
appearToClass | 首次出现结束状态类名 |
过渡模式
当两个元素切换时(如 show ? <A /> : <B />),可以通过 mode 属性控制进入和离开动画的顺序:
| 模式 | 行为 |
|---|---|
| 默认(无 mode) | 进入和离开动画同时进行 |
out-in | 当前元素先离开,新元素后进入 |
in-out | 新元素先进入,当前元素后离开 |
tsx
{
/* out-in:先离开再进入,更常用 */
}
;<Transition name="fade" mode="out-in">
{show.value ? <div key="a">内容 A</div> : <div key="b">内容 B</div>}
</Transition>appear — 首次渲染动画
默认情况下,Transition 不会在组件首次渲染时触发动画。设置 appear={true} 可以让首次渲染也触发进入动画:
tsx
<Transition name="fade" appear>
<div>首次渲染也会有淡入效果</div>
</Transition>JavaScript 钩子
除了 CSS 过渡,你还可以通过 JavaScript 钩子来控制动画。这在需要更复杂的动画效果(如使用 GSAP 等动画库)时很有用:
tsx
<Transition
css={false}
onBeforeEnter={(el) => {
// 进入动画开始前
el.style.opacity = '0'
}}
onEnter={(el, done) => {
// 进入动画开始,需手动调用 done() 表示完成
el.style.transition = 'opacity 0.3s'
el.style.opacity = '1'
setTimeout(done, 300)
}}
onAfterEnter={(el) => {
// 进入动画完成后
console.log('进入动画完成')
}}
onBeforeLeave={(el) => {
// 离开动画开始前
}}
onLeave={(el, done) => {
// 离开动画开始,需手动调用 done() 表示完成
el.style.transition = 'opacity 0.3s'
el.style.opacity = '0'
setTimeout(done, 300)
}}
onAfterLeave={(el) => {
// 离开动画完成后
console.log('离开动画完成')
}}
>
{show.value && <div>JS 动画</div>}
</Transition>设置
css={false}可以跳过 CSS 类名的检测,只使用 JavaScript 钩子。
完整的钩子列表:
| 钩子 | 参数 | 说明 |
|---|---|---|
onBeforeEnter | (el) | 进入动画开始前 |
onEnter | (el, done) | 进入动画开始,需调用 done() |
onAfterEnter | (el) | 进入动画完成后 |
onEnterCancelled | (el) | 进入动画被取消时 |
onBeforeLeave | (el) | 离开动画开始前 |
onLeave | (el, done) | 离开动画开始,需调用 done() |
onAfterLeave | (el) | 离开动画完成后 |
onLeaveCancelled | (el) | 离开动画被取消时 |
onBeforeAppear | (el) | 首次出现动画开始前 |
onAppear | (el, done) | 首次出现动画开始,需调用 done() |
onAfterAppear | (el) | 首次出现动画完成后 |
onAppearCancelled | (el) | 首次出现动画被取消时 |
TransitionGroup — 列表过渡
TransitionGroup 用于为列表项添加进入、离开和移动的过渡动画。它基于 For 组件,支持 For 的所有属性,并额外提供了过渡动画能力。
tsx
import { TransitionGroup, reactive } from 'vitarx'
function App() {
const items = reactive([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' }
])
let nextId = 4
const addItem = () => {
items.push({ id: nextId++, text: `项目 #123;nextId - 1}` })
}
const removeItem = (id: number) => {
const index = items.findIndex((item) => item.id === id)
if (index !== -1) items.splice(index, 1)
}
return (
<div>
<button onClick={addItem}>添加</button>
<TransitionGroup each={items} key="id" name="list" tag="ul">
{(item) => <li onClick={() => removeItem(item.id)}>{item.text}(点击删除)</li>}
</TransitionGroup>
</div>
)
}对应的 CSS:
css
/* 进入和离开动画 */
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
/* 移动动画 — 关键!让列表项移动时有平滑过渡 */
.list-move {
transition: transform 0.5s ease;
}TransitionGroup 的额外属性:
| 属性 | 类型 | 说明 |
|---|---|---|
tag | string | 包裹子节点的标签名,不传则不创建包裹元素 |
moveClass | string | 元素移动时的 CSS 类名,默认为 ${name}-move |
props | object | 传递给包裹元素的属性 |
重要:
TransitionGroup的移动动画需要在 CSS 中定义${name}-move类,否则列表项位置变化时不会有动画效果。
完整示例
下面是一个包含条件渲染过渡和列表过渡的完整示例:
tsx
import { Transition, TransitionGroup, ref, reactive } from 'vitarx'
/** 条件渲染过渡示例 */
function ToggleDemo() {
const show = ref(true)
return (
<div style="margin-bottom: 32px;">
<h2>条件渲染过渡</h2>
<button
onClick={() => {
show.value = !show.value
}}
>
{show.value ? '隐藏' : '显示'}
</button>
<Transition name="slide-fade" mode="out-in">
{show.value ? (
<div key="visible" class="box">
我可见
</div>
) : (
<div key="hidden" class="box hidden-state">
我已隐藏
</div>
)}
</Transition>
</div>
)
}
/** 列表过渡示例 */
function ListDemo() {
const items = reactive([
{ id: 1, text: '学习 Vitarx' },
{ id: 2, text: '编写组件' },
{ id: 3, text: '部署项目' }
])
let nextId = 4
const addItem = () => {
items.push({ id: nextId++, text: `新任务 #123;nextId - 1}` })
}
const removeItem = (id: number) => {
const index = items.findIndex((item) => item.id === id)
if (index !== -1) items.splice(index, 1)
}
return (
<div>
<h2>列表过渡</h2>
<button onClick={addItem}>添加任务</button>
<TransitionGroup each={items} key="id" name="list" tag="ul">
{(item) => (
<li onClick={() => removeItem(item.id)} style="cursor: pointer; padding: 4px 0;">
{item.text}
</li>
)}
</TransitionGroup>
</div>
)
}
function App() {
return (
<div style="padding: 20px;">
<ToggleDemo />
<ListDemo />
</div>
)
}对应的 CSS:
css
/* slide-fade 过渡 */
.slide-fade-enter-from {
opacity: 0;
transform: translateX(-20px);
}
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-from {
opacity: 1;
}
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-leave-to {
opacity: 0;
transform: translateX(20px);
}
/* 列表过渡 */
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-move {
transition: transform 0.5s ease;
}