[JavaScript] ResizeObserver

block 和 none 问题

做折叠面板最简单的方式是改变它的 blocknone,这两个属性值不包含在可动画属性中。详见:CSS animated properties。所以,设置 CSS 动画(keyframes)或 transition 都是没有效果的。

ResizeObserver 观察节点

  1. 如果 slot 接受的内容是需等待异步数据完成之后才渲染,在 onMounted 时获取节点的高度就不准确。
  2. slot 标签不可以添加 ref,因此,不可以通过 watch 观察内容的变化,以重新获取内容高度。
  3. 如果 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>

实现效果

效果演示

posted @ 2023-03-03 01:31  Himmelbleu  阅读(233)  评论(0编辑  收藏  举报