前端插件-不固定高度的DIV如何增加transition

这篇文章主要讲怎么给不固定高度的div增加过渡效果,涉及前端开发领域

需要使用者具备下面的开发环境

  1. typescript
  2. vue3
  3. sass

那熟悉前端开发的同学都知道,想要给一个div增加过渡效果(transition),就必须给这个div设置一个高度值,如果高度值没有设置,那么transition属性就会失效

设计思路

由于我们是要做一个通用的组件,可以让别人开箱即用,那么就需要用到vue里面很重要的一个功能,插槽

1. 放置slot

首先在我们的组件内部放置一个slot,那么接下来就只需要拿到这个slot的一个高度,我的想法是这样的,在slot外部再包一个div,然后给这个div加上ref属性,当然这个div也是不固定高度,因为要随着slot高度变化而变化

html 复制代码
    <div :id="id" ref="contentRef">
      <slot></slot>
    </div>

那接下来我们就面临一个难题,怎么去监听slot高度,或者说监听contentRef的高度

2. ResizeObserver

什么是ResizeObserverResuseObserver 是一个现代的浏览器 API,它用于监听元素尺寸的变化 。你可以把它想象成专门针对元素大小(widthheight)的 MutationObserverIntersectionObserver

在它出现之前,要监听一个元素的大小变化非常麻烦且低效,通常需要依赖监听 windowresize 事件,然后通过循环或复杂的方式去检查目标元素的大小是否改变了。ResizeObserver 提供了一个高效、专一且性能良好的解决方案。

为什么要用ResizeObserver:

  1. 响应式设计: 当元素的尺寸因CSS媒体查询、容器查询(Container Queries)、Flexbox或Grid布局的重新计算、内容增减、甚至是窗口缩放而改变时,我们需要执行一些操作(例如,重新绘制图表、调整地图大小、重新布局等)。
  2. 性能: 相比于在 window.resize 上绑定事件然后循环检查所有元素的古老方法,ResizeObserver 只在特定元素尺寸变化时才会触发回调,大大提升了性能。
  3. 替代有问题的 window.resize window.resize 事件会在用户每一次微调浏览器窗口时频繁触发,容易导致性能问题。而且它只能监听窗口变化,无法直接知道是哪个具体元素的大小变了。

然后在我们的的组件中就可以这样进行使用

ts 复制代码
const contentRef = ref<HTMLElement | null>(null)
const state = ref<boolean>(props.initState)
const height = ref<undefined | string>(undefined)
let observer: ResizeObserver | null = null

// 初始化尺寸观察
const initResizeObserver = (): void => {
  if (!contentRef.value) return

  observer = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const newHeight = entry.contentRect.height
      // 当处于展开状态时同步高度
      if (state.value) {
        height.value = `${newHeight + 3}px`
      }
    }
  })

  observer.observe(contentRef.value)
}

// 生命周期
onMounted(() => {
  props.initState ? expand() : contract()
  initResizeObserver()
})

当我们new了一个ResizeObserver实例时,就需要传入一个回调函数,这个函数就是当尺寸发生变化时,我们需要做的处理

而observe()方法就是监听的对象,那么我们可以看到,我是把contentRef.value对象传了进去,至于为什么不传入slot对象,这也是因为涉及到了一个很重要的开发思想,不要相信用户传入的东西

那么到此我们就已经实时获取contentRef的一个高度值,那接下来我们增加过渡效果

3. 增加过渡效果

若要增加过渡效果,就比不可能在contentRef上面加transition属性,因为会失效,所以我们就需要在contentRef外面再包一层div,在这个div增加transition属性

html 复制代码
<template>
  <div class="size-transition" :style="{ height }">
    <div :id="id" ref="contentRef">
      <slot></slot>
    </div>
  </div>
</template>
css 复制代码
.size-transition {
  width: 100%;
  overflow: hidden;
  transition: height 0.3s ease-in-out;
  will-change: height; // 启用硬件加速
}

由于我们前面已经获取了height最新高度,所以我们可以直接将这个高度传入到style里面

到此我们的组件就设计完毕了

完整源代码

html 复制代码
<template>
  <div class="size-transition" :style="{ height }">
    <div :id="id" ref="contentRef">
      <slot></slot>
    </div>
  </div>
</template>

<script setup lang="ts">
import { generateUUID } from '@renderer/utils/utils'
import { ref, onMounted, onBeforeUnmount } from 'vue'

const props = defineProps({
  minHeight: {
    type: Number,
    default: 0
  },
  initState: {
    type: Boolean,
    default: true
  }
})

const id = ref<string>(generateUUID())
const contentRef = ref<HTMLElement | null>(null)
const state = ref<boolean>(props.initState)
const height = ref<undefined | string>(undefined)
let observer: ResizeObserver | null = null

// 初始化尺寸观察
const initResizeObserver = (): void => {
  if (!contentRef.value) return

  observer = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const newHeight = entry.contentRect.height
      // 当处于展开状态时同步高度
      if (state.value) {
        height.value = `${newHeight + 3}px`
      }
    }
  })

  observer.observe(contentRef.value)
}

// 折叠方法
const contract = (): void => {
  if (!contentRef.value) return
  height.value = `${contentRef.value.offsetHeight}px`
  requestAnimationFrame(() => {
    height.value = `${props.minHeight}px`
    state.value = false
  })
}

// 展开方法
const expand = (): void => {
  if (!contentRef.value) return
  height.value = '0px'
  requestAnimationFrame(() => {
    height.value = `${contentRef.value!.offsetHeight + 3}px`
    state.value = true
  })
}

// 切换方法
const zoom = (): void => {
  state.value ? contract() : expand()
}

// 生命周期
onMounted(() => {
  props.initState ? expand() : contract()
  initResizeObserver()
})

onBeforeUnmount(() => {
  observer?.disconnect()
})

defineExpose({
  contract,
  expand,
  zoom
})
</script>

<style scoped lang="scss">
.size-transition {
  width: 100%;
  overflow: hidden;
  transition: height 0.3s ease-in-out;
  will-change: height; // 启用硬件加速
}
</style>

实际应用与效果

实际应用也很简单,只需要导入,然后将自己的div放入到里面即可

html 复制代码
<template>
    <SizeTransition>
        <div>
            可变高度
        </div>
    </SizeTransition>
</template>
import SizeTransition from '@renderer/components/sizeTransition/SizeTransition.vue'

接下来我们看看效果,大家看图一和图二,这是两个高度不同的div

图一

图二

当我们点击tab栏进行切换时【节点数据/节点样式进行切换】

图三

实际的过渡效果如图四所示

图四

谢谢大家观看

相关推荐
李鸿耀21 分钟前
主题换肤指南:设计到开发的完整实践
前端
带娃的IT创业者5 小时前
TypeScript + React + Ant Design 前端架构入门:搭建一个 Flask 个人博客前端
前端·react.js·typescript
非凡ghost6 小时前
MPC-BE视频播放器(强大视频播放器) 中文绿色版
前端·windows·音视频·软件需求
Stanford_11066 小时前
React前端框架有哪些?
前端·微信小程序·前端框架·微信公众平台·twitter·微信开放平台
洛可可白7 小时前
把 Vue2 项目“黑盒”嵌进 Vue3:qiankun 微前端实战笔记
前端·vue.js·笔记
学习同学7 小时前
从0到1制作一个go语言游戏服务器(二)web服务搭建
服务器·前端·golang
-D调定义之崽崽7 小时前
【初学】调试 MCP Server
前端·mcp
四月_h8 小时前
vue2动态实现多Y轴echarts图表,及节点点击事件
前端·javascript·vue.js·echarts
文心快码BaiduComate8 小时前
用Zulu轻松搭建国庆旅行4行诗网站
前端·javascript·后端
行者..................9 小时前
手动编译 OpenCV 4.1.0 源码,生成 ARM64 动态库 (.so),然后在 Petalinux 中打包使用。
前端·webpack·node.js