---
title: "Transition 动效"
description: "用于内容切换、列表与推入翻页的过渡封装"
category: Effects
status: beta
since: 0.3.4
tags: [transition, motion, list, auto-size, navigation]
syncStatus: reviewed
verified: true
---

## 用法

### 内容切换
单个带 key 的子节点，key 变化即切换；`preset` 选择过渡，`smooth-size` 同时平滑容器尺寸。
:::TuffDemoWrapper{demo="TransitionTransitionContentDemo" code-lang="vue"}
---
code: |
  <template>
    <TxTransition :preset="preset" :duration="220" mode="out-in">
      <div :key="value">Panel {{ value }}</div>
    </TxTransition>

    <TxTransitionFade :duration="180" mode="out-in">
      <div :key="`fade-${value}`">Fade {{ value }}</div>
    </TxTransitionFade>

    <TxTransitionSlideFade :duration="180" mode="out-in">
      <div :key="`slide-${value}`">SlideFade {{ value }}</div>
    </TxTransitionSlideFade>

    <TxTransitionRebound :duration="200" mode="out-in">
      <div :key="`rebound-${value}`">Rebound {{ value }}</div>
    </TxTransitionRebound>
  </template>
---
:::

### 列表增删
`group` 改用 `TransitionGroup`，每项都需要稳定 key。
:::TuffDemoWrapper{demo="TransitionTransitionListDemo" code-lang="vue"}
---
code: |
  <template>
    <TxTransition preset="slide-fade" group tag="div" :duration="180">
      <div v-for="item in items" :key="item.id">
        {{ item.text }}
      </div>
    </TxTransition>
  </template>
---
:::

### 推入翻页
层级导航用 `TxTransitionPush`：进入子页传 `direction="forward"`，返回传 `"back"`，子元素的 key 变化即翻页。
:::TuffDemoWrapper{demo="TransitionTransitionPushDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const pages = [
    { id: 'actions', title: '操作', rows: ['复制路径', '在访达中显示', '流转到…'] },
    { id: 'targets', title: '选择目标', rows: ['剪贴板历史', '系统信息', '快速笔记', '翻译', '截图标注'] },
    { id: 'confirm', title: '确认', rows: ['仅本次允许', '始终允许'] },
  ]

  const depth = ref(0)
  const direction = ref<'forward' | 'back'>('forward')
  const page = computed(() => pages[depth.value])

  function next() {
    direction.value = 'forward'
    depth.value = Math.min(depth.value + 1, pages.length - 1)
  }

  function back() {
    direction.value = 'back'
    depth.value = Math.max(depth.value - 1, 0)
  }
  </script>

  <template>
    <TxButton :disabled="depth === 0" @click="back">返回</TxButton>
    <TxButton :disabled="depth === pages.length - 1" @click="next">下一页</TxButton>

    <TxTransitionPush :direction="direction">
      <ul :key="page.id">
        <li v-for="row in page.rows" :key="row">{{ row }}</li>
      </ul>
    </TxTransitionPush>
  </template>
---
:::

### 最佳实践

- 带 key 的面板切换用 `mode="out-in"`，旧内容先离场、新内容再进入。
- 平级的标签切换不是推入，继续用淡入淡出；`TxTransitionPush` 只用于层级导航。
- 内容高度变化时用 `smooth-size`；列表和重复行用 `fade`、`slide-fade` 或 `rebound`。
- 列表 key 用稳定的业务 id，可排序列表不要用临时下标。
- 高频动效保持短时长；`rebound` 只用于允许轻微回弹的小面积反馈。

## API 参考

### TxTransition

#### 属性

| 属性名 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `preset` | `'fade' \| 'slide-fade' \| 'rebound' \| 'smooth-size'` | `'fade'` | 过渡预设；`smooth-size` 仅在 `group=false` 时委托 `TxTransitionSmoothSize`。 |
| `group` | `boolean` | `false` | 用 `TransitionGroup` 渲染带 key 的列表。 |
| `tag` | `string` | `'div'` | `group=true` 时的根标签。 |
| `appear` | `boolean` | `true` | 首次渲染时执行进入动效。 |
| `mode` | `'in-out' \| 'out-in'` | `'out-in'` | 单子节点的 `Transition` 模式；`group` 下不生效。 |
| `duration` | `number` | `180` | 时长（ms），写入 `--tx-transition-duration`。 |
| `easing` | `string` | `'cubic-bezier(0.2, 0, 0, 1)'` | 缓动函数，写入 `--tx-transition-easing`；`rebound` 进入时用自带的弹性曲线。 |

#### 插槽

| 插槽名 | Props | 说明 |
|------|-------|------|
| `default` | - | 单个带 key 的子节点；`group=true` 时为多个带稳定 key 的子节点。 |

### TxTransitionSmoothSize

#### 属性

| 属性名 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `appear` | `boolean` | `true` | 首次渲染时执行进入动效。 |
| `mode` | `'in-out' \| 'out-in'` | `'out-in'` | 内部带 key 子节点的 `Transition` 模式。 |
| `duration` | `number` | `220` | 尺寸过渡与内容动效共用的时长。 |
| `easing` | `string` | `'cubic-bezier(0.2, 0, 0, 1)'` | 尺寸与内容动效共用的缓动函数。 |
| `width` | `boolean` | `false` | 动画宽度变化。 |
| `height` | `boolean` | `true` | 动画高度变化。 |
| `motion` | `'fade' \| 'slide-fade' \| 'rebound'` | `'fade'` | 尺寸变化期间内部内容的动效。 |

### TxTransitionPush

#### 属性

| 属性名 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `direction` | `'forward' \| 'back'` | `'forward'` | `forward` 新页从行内结束方向推入，`back` 相反；RTL 下自动镜像。 |
| `duration` | `number` | `220` | 推入与高度过渡的时长（ms）；`0` 直接替换。 |
| `easing` | `string` | `'cubic-bezier(0.23, 1, 0.32, 1)'` | 推入、淡化与高度过渡共用；由 Web Animations 读取，不能写 CSS 变量。 |
| `height` | `boolean` | `true` | 切换时容器高度从旧页过渡到新页，结束后回到 `auto`。 |
| `appear` | `boolean` | `false` | 首次挂载时第一页也按 `direction` 推入。 |

#### 事件

| 事件名 | 参数 | 说明 |
|------|------|------|
| `before-enter` | `(el: Element)` | 新页插入之前触发。 |
| `after-enter` | `(el: Element)` | 新页推入完成后触发；被下一次切换打断的进入不触发。 |
| `after-leave` | `(el: Element)` | 旧页移出 DOM 后触发，包括中途被同 key 的新页替换。 |

#### 插槽

| 插槽名 | Props | 说明 |
|------|-------|------|
| `default` | - | 恰好一个带 key 的子元素，key 变化即翻页。 |

### 语义化组件

| 组件 | 预设 |
|------|------|
| `TxTransitionFade` | `fade` |
| `TxTransitionSlideFade` | `slide-fade` |
| `TxTransitionRebound` | `rebound` |
| `TxTransitionSmoothSize` | 尺寸感知封装，可配置内部 `motion` |
| `TxTransitionPush` | 层级翻页的独立实现，不走 `preset` |

## 概述

- `class` 与 `style` 合并到外层 `.tx-transition`；其余 attrs（含 Vue 过渡监听器）转发给 `Transition` / `TransitionGroup`，`smooth-size` 下转发给 `TxAutoSizer`。
- `smooth-size` 是单子节点模式：用 `TxAutoSizer` 包住内容，默认只动画高度，测量期间隐藏溢出；`group=true` 时按 `fade` 处理。
- `TxTransitionPush` 的新旧两页同时运动：旧页原位固定为 `position: absolute` 并加上 `inert`，新页留在文档流里决定高度；旧页里的焦点随之丢失，换页时由使用方交给新页。
- 推入期间容器是 `overflow: clip`（不是 `hidden`），静止时不裁剪，页内焦点环不会被切掉。
- 切换被打断时从当前绘制的位置继续；推入中途回到同一个 key 时，回来的页从旧页停下的位置滑回。
- 减少动态效果时各预设收敛到近乎瞬时，并去掉位移与缩放；`TxTransitionPush` 改为 120ms 的原位淡入淡出，高度直接落定。

## 技术实现

- `TxTransitionPush` 用 `<Transition :css="false">` 的 JS 钩子驱动 Web Animations：页面动 `translate`，不占用自身的 `transform`；容器动 `height`；被打断的页按 `getComputedTiming().progress` 续跑。
- 源码：`packages/tuffex/packages/components/src/transition/`。

<TuffDocSourceLink />
