【ResizeObserver】【页面布局】监听一个 div 元素的动态高度变化并同步设置另一个元素的高度
在 Vue3 中监听一个 div 元素的动态高度变化并同步设置另一个元素的高度,最佳实践是使用 ResizeObserver API。
在开发中,常有上中下分布列表页,如下:
需求现象 :列表滚动区域需要具体高度来滚动,但是搜索区域会随页面拉伸等变化高度。
处理方法一:最佳方案:使用 ResizeObserver
- ResizeObserver API:
现代浏览器原生支持,高效监听元素尺寸变化
比轮询或MutationObserver性能更好 - 生命周期管理:
onMounted中初始化观察器
onBeforeUnmount中清理观察器,避免内存泄漏
- 初始高度设置:
在开始观察前先同步一次初始高度 - 平滑过渡:
通过CSS transition实现高度变化的动画效果
<template><div class="container"><!-- 高度会动态变化的源元素 --><div ref="sourceRef" class="source-element">内容可能动态变化导致高度改变...</div><!-- 需要同步高度的目标元素 --><div ref="targetRef" class="target-element">我的高度会跟随上方元素</div></div></template><script setup>import { ref, onMounted, onBeforeUnmount } from 'vue'const sourceRef = ref(null)const targetRef = ref(null)let resizeObserver = null// 处理高度变化的回调函数const handleResize = (entries) => {if (!targetRef.value) returnfor (let entry of entries) {const { height } = entry.contentRecttargetRef.value.style.height = `${height}px`// 也可以使用CSS变量:// targetRef.value.style.setProperty('--dynamic-height', `${height}px`)}}onMounted(() => {// 初始化观察器resizeObserver = new ResizeObserver(handleResize)if (sourceRef.value) {// 开始观察源元素resizeObserver.observe(sourceRef.value)// 初始设置一次高度const initHeight = sourceRef.value.getBoundingClientRect().heighttargetRef.value.style.height = `${initHeight}px`}})onBeforeUnmount(() => {// 组件卸载时停止观察if (resizeObserver) {resizeObserver.disconnect()}// 或// resizeObserver?.disconnect()})</script><style>.source-element {border: 1px solid #ccc;padding: 20px;/* 高度由内容决定 */}.target-element {border: 1px solid #f0f;margin-top: 10px;overflow: hidden;transition: height 0.3s ease; /* 添加平滑过渡效果 */}</style>
优化—响应式地存储高度值
这种方法将高度值存储在响应式变量中,便于在其他地方使用。
<script setup>import { ref, onMounted, onBeforeUnmount } from 'vue'const sourceRef = ref(null)const targetRef = ref(null)const dynamicHeight = ref(0)let resizeObserver = nullonMounted(() => {resizeObserver = new ResizeObserver((entries) => {entries.forEach(entry => {dynamicHeight.value = entry.contentRect.height})})if (sourceRef.value) {resizeObserver.observe(sourceRef.value)}})// 使用watch监听高度变化watch(dynamicHeight, (newHeight) => {if (targetRef.value) {targetRef.value.style.height = `${newHeight}px`}})onBeforeUnmount(() => {resizeObserver?.disconnect()})</script>
使用 @resize 事件(需配合自定义指令)
如果项目中频繁需要监听尺寸变化,可以封装一个自定义指令:
// directives/resize.js
export default {mounted(el, binding) {const callback = binding.value;const observer = new ResizeObserver((entries) => {callback(entries[0].contentRect);});observer.observe(el);el._resizeObserver = observer;},unmounted(el) {if (el._resizeObserver) {el._resizeObserver.disconnect();}},
};
<template><div v-resize="onSourceResize">源元素</div><div :style="{ height: targetHeight + 'px' }">目标元素</div>
</template><script setup>
import { ref } from 'vue';
import resizeDirective from './directives/resize';const targetHeight = ref(0);const onSourceResize = (rect) => {targetHeight.value = rect.height;
};
</script>
注意事项:
性能:ResizeObserver 是异步触发的,避免在回调中执行高耗能操作。
兼容性:如果需要支持旧浏览器,需引入 resize-observer-polyfill。
初始高度:在 onMounted 中可能需要手动设置一次初始高度。
完整示例(基于 ResizeObserver)
<template><div ref="source" class="source"><p>源元素内容(高度可能变化)</p><button @click="addContent">增加内容</button></div><div ref="target" class="target" :style="{ height: targetHeight + 'px' }">目标元素(高度同步源元素)</div>
</template><script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';const source = ref(null);
const target = ref(null);
const targetHeight = ref(0);
let observer = null;const updateHeight = () => {if (source.value) {targetHeight.value = source.value.offsetHeight;}
};onMounted(() => {observer = new ResizeObserver(updateHeight);if (source.value) {observer.observe(source.value);updateHeight(); // 初始化高度}
});onBeforeUnmount(() => {if (observer) observer.disconnect();
});const addContent = () => {const p = document.createElement('p');p.textContent = '新增内容 ' + Math.random();source.value.appendChild(p);
};
</script><style>
.source {border: 1px solid blue;padding: 10px;margin-bottom: 10px;
}
.target {border: 1px solid red;padding: 10px;background: #eee;
}
</style>