Collapes
折叠面板
效果
标题
思路
🐘必须通过 v-show
控制显示隐藏
1. 打开面板
- 设置
height
为 "空字符串",是为了触发重排,也可以使用其他方式`` - 设置
v-show
为true
,此时还没有渲染 - 使用
requestAnimationFrame
回调中 获取元素的高度offsetHeight
,设置高度为0px
- 再上一个的
requestAnimationFrame
的回调中继续使用requestAnimationFrame
,把 高度设置为offsetHeight
2. 关闭面板
从高度 offsetHeight
滚动到 0px
TIP
核心在于 requestAnimationFrame
的使用,第一个动画帧设置高度为 0px
,第二个动画帧设置高度为 offsetHeight
,这样才会有动画
height:''的原因
设置为''是为了有一个高度变化,设置 auto 元素的高度由内容决定,浏览器无法预先知道这个值
设置为 0 无法获取自然高度
核心代码
ts
const openPanel = () => {
if (!contentEl.value) return
if (showContent.value) return;
(contentEl.value as unknown as HTMLElement).style.height = "";
// 此时还未渲染
showContent.value = true;
requestAnimationFrame(() => {
if (!contentEl.value) return
const { offsetHeight } = contentEl.value;
(contentEl.value as unknown as HTMLElement).style.height = "0px";
requestAnimationFrame(() => {
(contentEl.value as unknown as HTMLElement).style.height = offsetHeight + 'px';
})
})
}
const closePanel = () => {
if (!contentEl.value) return
const { offsetHeight } = contentEl.value
contentEl.value.style.height = offsetHeight + 'px'
requestAnimationFrame(() => {
; (contentEl.value as HTMLDivElement).style.height = 0 + 'px'
})
}
源码
vue
<template>
<div>
<p @click="toggle">title</p>
<div v-show="showContent" class="content" ref="contentEl" @transitionend="transitionend">
<div>
<slot />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, reactive, watch } from "vue";
const showContent = ref(false);
const contentEl = ref<HTMLElement | null>(null);
const isShow = ref(false);
watch(isShow, (value) => {
if (value) openPanel()
else closePanel()
})
const transitionend = () => {
if (!isShow.value) {
showContent.value = false;
}
}
const toggle = () => {
isShow.value = !isShow.value
}
const openPanel = () => {
if (!contentEl.value) return
if (showContent.value) return;
(contentEl.value as unknown as HTMLElement).style.height = "";
showContent.value = true;
requestAnimationFrame(() => {
if (!contentEl.value) return
const { offsetHeight } = contentEl.value;
(contentEl.value as unknown as HTMLElement).style.height = "0px";
requestAnimationFrame(() => {
(contentEl.value as unknown as HTMLElement).style.height = offsetHeight + 'px';
})
})
}
const closePanel = () => {
if (!contentEl.value) return
const { offsetHeight } = contentEl.value
contentEl.value.style.height = offsetHeight + 'px'
requestAnimationFrame(() => {
; (contentEl.value as HTMLDivElement).style.height = 0 + 'px'
})
}
</script>
<style lang="scss" scoped>
.content {
overflow: hidden;
transition: height 0.25s;
}
</style>