[JavaScript] ResizeObserver
block 和 none 问题
做折叠面板最简单的方式是改变它的 block
或 none
,这两个属性值不包含在可动画属性中。详见:CSS animated properties。所以,设置 CSS 动画(keyframes)或 transition 都是没有效果的。
ResizeObserver 观察节点
- 如果 slot 接受的内容是需等待异步数据完成之后才渲染,在 onMounted 时获取节点的高度就不准确。
- slot 标签不可以添加 ref,因此,不可以通过 watch 观察内容的变化,以重新获取内容高度。
- 如果 slot 插入了 element-plus 的折叠面板,展开之后高度增加,因此高度是一个不确定的值,高度不够使得内容被隐藏。
针对以上内容,我们需要一个东西,可以监听节点发生变化时触发、节点高度变化时也可以出发的观察者——ResizeObserver。
完整代码
<script setup>
const boxInst = ref();
const boxWrapInst = ref();
const isDownArrow = ref(true);
const boxWrapInstHeight = ref(0);
let lastBoxWrapInstHeight = 0;
// 开启监听,当异步函数渲染完成之后最后一次高度就是最终的高度
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
boxWrapInstHeight.value = entry.target.offsetHeight;
});
});
function toggle() {
// 当收起面板时,将 boxWrapInstHeight 设置为 0,那么整个面板就没了
if (isDownArrow.value) {
// 收起面板,记录关闭之前的节点高度,再次打开时把值还给面板
lastBoxWrapInstHeight = boxWrapInstHeight.value;
boxWrapInstHeight.value = 0;
} else {
boxWrapInstHeight.value = lastBoxWrapInstHeight;
}
isDownArrow.value = !isDownArrow.value;
}
onMounted(() => {
observer.observe(boxWrapInst.value);
});
onUnmounted(() => {
observer.disconnect();
});
</script>
<template>
<div class="mb-6">
<div class="title select-none mb-4 f-c-b l-size-5 l-color-1">
<div class="f-c-s">
<div class="f-c-c mr-1">
<slot name="icon"></slot>
</div>
{{ text }}
</div>
<div @click="toggle" class="effect f-c-c" :class="{ 'arrow-up': !isDownArrow, 'arrow-down': isDownArrow }">
<div class="arrow effect f-c-c">
<i-ep-arrow-down />
</div>
</div>
</div>
<div class="effect" ref="boxInst" :style="{ height: boxWrapInstHeight + 'px' }">
<div ref="boxWrapInst">
<slot></slot>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.title {
padding-left: 0.4rem;
border-radius: 0.25rem;
border-left: 0.25rem solid var(--el-color-primary);
}
.title:hover .arrow {
transform: scale(1, 1);
}
.arrow {
transform: scale(0, 0);
}
.arrow-up {
transform: rotate(180deg);
}
.arrow-down {
transform: rotate(0deg);
}
.effect {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
</style>