You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确初始化DOM元素高度以实现height-transition过渡效果?

解决Vue中height:auto过渡的稳健方案

问题分析

核心矛盾是CSS无法直接在height: auto和固定高度间实现过渡,必须通过JS获取scrollHeight作为目标高度,但onMounted+nextTick或watch方案失效,本质是元素内部内容还未完全渲染/加载完成,导致此时获取的scrollHeight并非最终值。setTimeout靠延迟等待内容就绪,但这种方式不够可靠,容易受网络、渲染性能影响。

稳健解决方案

方案1:用ResizeObserver实时监听元素尺寸变化

ResizeObserver能监听元素的尺寸变化,不管是内容加载、子组件渲染完成还是窗口 resize,都能实时获取正确的scrollHeight:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const open = ref(true)
const state = ref({ height: '0' })
const tab = ref(null)
let resizeObserver = null

onMounted(() => {
  resizeObserver = new ResizeObserver(entries => {
    const { scrollHeight } = entries[0].target
    if (open.value) {
      state.value.height = `${scrollHeight}px`
    }
  })
  
  tab.value && resizeObserver.observe(tab.value)
})

onUnmounted(() => {
  if (resizeObserver && tab.value) {
    resizeObserver.unobserve(tab.value)
    resizeObserver.disconnect()
  }
})

const toggleOpen = () => {
  open.value = !open.value
  if (open.value && tab.value) {
    state.value.height = `${tab.value.scrollHeight}px`
  }
}
</script>

<template>
  <div
    class="transition-all duration-300 overflow-hidden"
    :style="{ height: open ? state.height : '0' }"
    ref="tab"
  >
    <!-- 你的组件内容 -->
  </div>
</template>

方案2:针对异步加载内容,监听数据就绪后计算高度

如果tab内部内容依赖异步数据(比如接口请求),必须等数据加载完成后再计算高度:

<script setup>
import { ref, onMounted, watch } from 'vue'

const open = ref(true)
const state = ref({ height: '0' })
const tab = ref(null)
// 模拟异步加载的内容数据
const tabContent = ref(null)

const fetchTabContent = async () => {
  // 替换为实际接口请求逻辑
  const res = await fetch('/api/get-tab-content')
  tabContent.value = await res.json()
}

onMounted(() => {
  fetchTabContent()
})

// 监听数据加载完成,等待DOM更新后计算高度
watch(tabContent, async () => {
  await nextTick()
  if (tab.value && open.value) {
    state.value.height = `${tab.value.scrollHeight}px`
  }
}, { deep: true })

const toggleOpen = () => {
  open.value = !open.value
  if (open.value && tab.value) {
    state.value.height = `${tab.value.scrollHeight}px`
  }
}
</script>

<template>
  <div
    class="transition-all duration-300 overflow-hidden"
    :style="{ height: open ? state.height : '0' }"
    ref="tab"
  >
    <!-- 渲染异步内容 -->
    <div v-for="item in tabContent" :key="item.id">{{ item.content }}</div>
  </div>
</template>

方案3:使用Vue Transition组件的钩子函数

借助<Transition>组件提供的精准过渡钩子,在过渡开始前计算正确高度:

<script setup>
import { ref } from 'vue'

const open = ref(true)
const tab = ref(null)

const beforeEnter = (el) => {
  el.style.height = `${el.scrollHeight}px`
}

const enter = (el) => {
  // 触发重绘,确保过渡动画生效
  el.offsetHeight
}

const beforeLeave = (el) => {
  el.style.height = `${el.scrollHeight}px`
}

const leave = (el) => {
  el.style.height = '0'
}
</script>

<template>
  <Transition
    @before-enter="beforeEnter"
    @enter="enter"
    @before-leave="beforeLeave"
    @leave="leave"
  >
    <div
      v-show="open"
      class="transition-all duration-300 overflow-hidden"
      ref="tab"
    >
      <!-- 你的组件内容 -->
    </div>
  </Transition>
</template>

原有方案失效原因

  • onMounted + nextTick:仅能等待当前组件挂载后的DOM更新,无法覆盖内部异步内容、子组件异步渲染的场景,此时scrollHeight仍未就绪。
  • 监听scrollHeight:scrollHeight是DOM原生属性,不属于Vue响应式变量,无法触发watch回调。
  • 监听tab.value:元素挂载后自身不会变化,deep监听也无法捕捉内部内容变化带来的尺寸改变。

内容的提问来源于stack exchange,提问作者Artur Müller Romanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:25:26