【ResizeObserver】【页面布局】监听一个 div 元素的动态高度变化并同步设置另一个元素的高度

发布于:2025-07-25 ⋅ 阅读:(13) ⋅ 点赞:(0)

在 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) return
	  
	  for (let entry of entries) {
	    const { height } = entry.contentRect
	    targetRef.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().height
	    targetRef.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 = null
	
	onMounted(() => {
	  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>

代替方案比较:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到