通用组件
AutoSizer 自适应尺寸
用于让容器在内容变化时自动跟随宽/高,并带过渡动画。
该页面由 AI 迁移生成,请谨慎使用
内容已迁移完成,但仍建议结合源码和人工评审结果使用。
AutoSizer 自适应尺寸
用于让容器在内容变化时自动跟随宽/高,并带过渡动画。
典型场景:
- Tabs 切换内容时外层高度平滑过渡
- Button 在
loading状态切换时宽度平滑过渡
实现基于
ResizeObserver。当内容中包含图片/异步渲染导致尺寸再次变化时,会自动触发重新测量。
基础用法
仅高度(推荐用于 Tabs/Accordion)
AutoSizer height
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const active = ref<'a' | 'b'>('a')
const sizerRef = ref<any>(null)
function setTab(next: 'a' | 'b') {
void sizerRef.value?.action?.(() => {
active.value = next
})
}
</script>
<template>
<div style="width: 420px; max-width: 100%;">
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
<TxButton :variant="active === 'a' ? 'primary' : 'secondary'" @click="setTab('a')">
Tab A
</TxButton>
<TxButton :variant="active === 'b' ? 'primary' : 'secondary'" @click="setTab('b')">
Tab B
</TxButton>
</div>
<TxAutoSizer
ref="sizerRef"
:width="false"
:height="true"
:duration-ms="250"
outer-class="overflow-hidden"
style="border: 1px solid var(--tx-border-color); border-radius: 12px; padding: 12px;"
>
<div v-if="active === 'a'">
<div style="font-weight: 600; margin-bottom: 8px;">
Tab A
</div>
<div style="color: var(--tx-text-color-secondary);">
Short content.
</div>
</div>
<div v-else>
<div style="font-weight: 600; margin-bottom: 8px;">
Tab B
</div>
<div style="color: var(--tx-text-color-secondary); line-height: 1.6;">
Long content. Long content. Long content. Long content. Long content. Long content.
</div>
<div style="height: 24px;" />
<div style="color: var(--tx-text-color-secondary); line-height: 1.6;">
More lines. More lines. More lines.
</div>
</div>
</TxAutoSizer>
</div>
</template>
宽度跟随(在 flex 容器内)
当 AutoSizer 处于 flex item 且父容器把它
flex: 1或者width: 100%撑满时,看起来就像“没跟随内容”。
AutoSizer width in flex
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const wide = ref(false)
const sizerRef = ref<any>(null)
function toggle() {
void sizerRef.value?.action?.(() => {
wide.value = !wide.value
})
}
</script>
<template>
<div
style="width: 520px; max-width: 100%; display: flex; align-items: center; gap: 12px; border: 1px solid var(--tx-border-color); border-radius: 12px; padding: 12px;"
>
<TxButton @click="toggle">
Toggle
</TxButton>
<TxAutoSizer ref="sizerRef" :width="true" :height="false" outer-class="overflow-hidden">
<TxButton variant="secondary">
{{ wide ? 'Very very long label' : 'Short' }}
</TxButton>
</TxAutoSizer>
<div style="flex: 1; text-align: right; color: var(--tx-text-color-secondary);">
Right Area
</div>
</div>
</template>
下拉内容高度跟随(用于筛选/搜索)
AutoSizer height for dropdown
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
const mode = ref<'short' | 'long'>('short')
const sizerRef = ref<any>(null)
function toggleOpen() {
void sizerRef.value?.action?.(() => {
open.value = !open.value
})
}
function toggleItems() {
void sizerRef.value?.action?.(() => {
mode.value = mode.value === 'short' ? 'long' : 'short'
})
}
</script>
<template>
<div>
<div style="display: flex; gap: 8px; align-items: center;">
<TxButton @click="toggleOpen">
Toggle dropdown
</TxButton>
<TxButton @click="toggleItems">
Toggle items
</TxButton>
</div>
<div style="height: 10px;" />
<div style="width: 320px; max-width: 100%; border: 1px solid var(--tx-border-color); border-radius: 12px; overflow: hidden;">
<div style="padding: 10px 12px; font-weight: 600;">
Dropdown Panel (mock)
</div>
<TxAutoSizer ref="sizerRef" :width="false" :height="true" outer-class="overflow-hidden" style="padding: 8px 12px;">
<div v-if="open" style="display: flex; flex-direction: column; gap: 8px;">
<TxButton variant="secondary" style="justify-content: flex-start;">
Item A
</TxButton>
<TxButton variant="secondary" style="justify-content: flex-start;">
Item B
</TxButton>
<TxButton v-if="mode === 'long'" variant="secondary" style="justify-content: flex-start;">
Item C
</TxButton>
<TxButton v-if="mode === 'long'" variant="secondary" style="justify-content: flex-start;">
Item D
</TxButton>
<TxButton v-if="mode === 'long'" variant="secondary" style="justify-content: flex-start;">
Item E
</TxButton>
</div>
</TxAutoSizer>
</div>
</div>
</template>
弹框内容高度跟随(模拟)
AutoSizer height for dialog
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const mode = ref<'short' | 'long'>('short')
const sizerRef = ref<any>(null)
function toggle() {
void sizerRef.value?.action?.(() => {
mode.value = mode.value === 'short' ? 'long' : 'short'
})
}
</script>
<template>
<div>
<div style="display: flex; gap: 8px; align-items: center;">
<TxButton @click="toggle">
Toggle content
</TxButton>
</div>
<div style="height: 10px;" />
<div style="width: 420px; max-width: 100%; border: 1px solid var(--tx-border-color); border-radius: 16px; overflow: hidden;">
<div style="padding: 12px 14px; font-weight: 600;">
Dialog (mock)
</div>
<TxAutoSizer ref="sizerRef" :width="false" :height="true" outer-class="overflow-hidden" style="padding: 12px 14px;">
<div v-if="mode === 'short'" style="color: var(--tx-text-color-secondary); line-height: 1.6;">
Short content.
</div>
<div v-else style="color: var(--tx-text-color-secondary); line-height: 1.6;">
Long content. Long content. Long content. Long content. Long content.
<div style="height: 12px;" />
More lines. More lines. More lines.
</div>
</TxAutoSizer>
<div
style="padding: 12px 14px; display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--tx-border-color);"
>
<TxButton variant="secondary">
Cancel
</TxButton>
<TxButton variant="primary">
Confirm
</TxButton>
</div>
</div>
</div>
</template>
仅宽度(推荐用于 Button 内容变化)
AutoSizer width
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const loading = ref(false)
const sizerRef = ref<any>(null)
function toggle() {
void sizerRef.value?.action?.(() => {
loading.value = !loading.value
})
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 10px;">
<TxButton @click="toggle">
Toggle loading
</TxButton>
<TxAutoSizer ref="sizerRef" :width="true" :height="false" outer-class="overflow-hidden">
<TxButton :loading="loading" variant="primary">
Submit
</TxButton>
</TxAutoSizer>
</div>
</template>
数字过渡 + 宽度跟随(NumberFlow)
AutoSizer + NumberFlow
示例即将加载...
<script setup lang="ts">
import NumberFlow from '@number-flow/vue'
import { ref } from 'vue'
const value = ref(123.45)
const sizerRef = ref<any>(null)
function shuffle() {
const next = Math.random() > 0.5
? 3243.6
: 1000000.12
void sizerRef.value?.action?.(() => {
value.value = next
})
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 10px;">
<TxButton @click="shuffle">
Shuffle
</TxButton>
<TxAutoSizer ref="sizerRef" :width="true" :height="false" outer-class="overflow-hidden">
<TxButton variant="secondary">
<span style="display: inline-flex; align-items: center; gap: 6px;">
<span style="opacity: 0.7;">$</span>
<NumberFlow :value="value" />
</span>
</TxButton>
</TxAutoSizer>
</div>
</template>
文本丝滑变换 + 宽度跟随(TextTransformer)
该示例同时开启
width + height跟随,并在外层设置overflow-hidden,用于裁剪 blur 的边缘溢出。
AutoSizer + TextTransformer
示例即将加载...
<script setup lang="ts">
import { ref } from 'vue'
const sizerRef = ref<any>(null)
const label = ref('Short')
const accent = ref(false)
const duration = ref(360)
const blurPx = ref(10)
function toggle() {
void sizerRef.value?.action?.(() => {
label.value = label.value === 'Short'
? 'Very very long label (blur + fade) that will be clipped while resizing'
: 'Short'
accent.value = !accent.value
})
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px;">
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<TxButton @click="toggle">
Toggle
</TxButton>
<div style="width: 220px;">
<div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
duration (ms)
</div>
<TxSlider v-model="duration" :min="160" :max="720" :step="10" :show-value="true" />
</div>
<div style="width: 220px;">
<div style="font-size: 12px; opacity: 0.72; margin-bottom: 6px;">
blur (px)
</div>
<TxSlider v-model="blurPx" :min="0" :max="24" :step="1" :show-value="true" />
</div>
</div>
<TxAutoSizer
ref="sizerRef"
:width="true"
:height="true"
:inline="true"
:duration-ms="duration"
easing="cubic-bezier(0.2, 0, 0, 1)"
outer-class="overflow-hidden"
>
<TxCard variant="plain" background="mask" :padding="12" :radius="14" style="max-width: 360px;">
<div style="font-size: 13px; line-height: 1.4;">
<TxTextTransformer
:text="label"
:duration-ms="duration"
:blur-px="blurPx"
:style="{ color: accent ? 'var(--tx-color-primary)' : 'var(--tx-text-color-primary)' }"
/>
</div>
</TxCard>
</TxAutoSizer>
</div>
</template>
API
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
as | string | div | outer 渲染标签 |
innerAs | string | div | inner 渲染标签 |
width | boolean | true | 是否同步宽度 |
height | boolean | true | 是否同步高度 |
inline | boolean | - | 是否使用 shrink-to-content(默认仅在 width=true && height=false 时启用) |
durationMs | number | 200 | 过渡时长(ms) |
easing | string | ease | 过渡曲线 |
outerClass | string | overflow-hidden | outer class |
innerClass | string | - | inner class |
rounding | 'none' | 'round' | 'floor' | 'ceil' | ceil | 测量值取整策略 |
immediate | boolean | true | mount 后是否立即测量 |
rafBatch | boolean | true | 是否使用 rAF 合并测量 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
refresh() | () => Promise<void> | 手动触发重新测量 |
flip(action) | (action: () => void | Promise<void>) => Promise<void> | 以 action 触发一次尺寸过渡(适合 Tabs 切换/明确动作) |
action(fn, options?) | (fn: (el: HTMLElement) => void | Promise<void>, options?: AutoSizerActionOptions | detect) => Promise<any> | 执行一次“事务动作”,内部自动前后快照检测并触发过渡(推荐用于 demo/复杂切换) |
size | Ref<{ width: number; height: number } | null> | 最新测量结果 |