ResizeObserver:实时追踪元素尺寸变化的神器

发布时间:2024-01-13 14:42 分类:HTML·CSS·JS

前言

在 Web 开发中,监测和响应元素尺寸的变化一直是一个常见需求。传统方案如监听 window.resize 事件或使用定时器轮询都存在性能问题或实现复杂的缺陷。而 ResizeObserver API 的出现,为我们提供了一个优雅且高效的解决方案。

ResizeObserver 是什么?

ResizeObserver 是浏览器提供的一个原生 API,用于监测 DOM 元素的尺寸变化。无论是因为窗口调整、动态内容变化还是 CSS 动画,只要元素的尺寸发生改变,ResizeObserver 都能够立即捕获到这个变化。

// 基本用法
const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    console.log('元素尺寸变化:', entry.contentRect);
  }
});

// 开始观察目标元素
observer.observe(document.querySelector('.target-element'));

为什么需要 ResizeObserver?

1. 传统方案的局限性

window.resize 事件

只能监听窗口变化,无法监听具体元素的变化,使用起来相对局限。

window.addEventListener('resize', () => {
  // 处理逻辑
});

定时器轮询

性能开销大,精确度低。

setInterval(() => {
  const element = document.querySelector('.target');
  const width = element.offsetWidth;
  const height = element.offsetHeight;
  // 处理逻辑
}, 1000);

2. ResizeObserver 的优势

  • 性能优化:只在元素实际改变时触发回调
  • 精确监测:可以监测任意 DOM 元素的尺寸变化
  • 实时响应:变化发生时立即触发回调
  • 避免回流:使用特殊的计算时机,减少页面重排

实际应用场景

1. 实现 Vue2 版本的 UseElementSize 组件

在 Vue3 中,我们可以直接使用 VueUse 库提供的 useElementSize 来监听元素尺寸。但在 Vue2 项目中,我们可以基于 ResizeObserver 实现一个类似的功能:

// useElementSize.js
export default {
  name: 'UseElementSize',
  
  data() {
    return {
      width: 0,
      height: 0
    }
  },

  mounted() {
    // 创建观察器实例
    this.observer = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect
        this.width = Math.round(width)
        this.height = Math.round(height)
        // 触发尺寸变化事件
        this.$emit('resize', { width: this.width, height: this.height })
      }
    })

    // 开始观察当前元素
    this.observer.observe(this.$el)
  },

  beforeDestroy() {
    // 组件销毁前清理观察器
    if (this.observer) {
      this.observer.disconnect()
    }
  },

  render(h) {
    // 通过作用域插槽传递尺寸数据
    return h('div', [
      this.$scopedSlots.default?.({
        width: this.width,
        height: this.height
      })
    ])
  }
}

使用示例:

<template>
  <div class="container">
    <UseElementSize v-slot="{ width, height }" @resize="onResize">
      <div class="target-element">
        当前尺寸:{{ width }} x {{ height }}
      </div>
    </UseElementSize>
  </div>
</template>

<script>
import UseElementSize from './useElementSize'

export default {
  components: {
    UseElementSize
  },
  
  methods: {
    onResize({ width, height }) {
      console.log('元素尺寸变化:', width, height)
    }
  }
}
</script>

<style>
.target-element {
  resize: both;
  overflow: auto;
  min-width: 100px;
  min-height: 100px;
  border: 1px solid #ccc;
  padding: 16px;
}
</style>

2. 响应式布局调整

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    const width = entry.contentRect.width;
    
    // 根据宽度动态调整布局
    if (width < 768) {
      entry.target.classList.add('mobile-layout');
    } else {
      entry.target.classList.remove('mobile-layout');
    }
  }
});

// 观察容器元素
observer.observe(document.querySelector('.container'));

3. 图表自适应调整

const chart = echarts.init(document.querySelector('.chart'));

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    // 图表容器尺寸变化时,自动调整图表大小
    chart.resize();
  }
});

observer.observe(chart.getDom());

4. 虚拟滚动优化

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    // 容器高度变化时,重新计算可视区域内应显示的元素
    updateVisibleItems(entry.contentRect.height);
  }
});

observer.observe(document.querySelector('.virtual-scroll-container'));

注意事项

1. 性能考虑

虽然 ResizeObserver 本身性能较好,但在回调中执行复杂操作仍需谨慎:

// 使用防抖优化频繁触发的回调
const debouncedCallback = debounce((entries) => {
  // 处理尺寸变化
}, 200);

const observer = new ResizeObserver(debouncedCallback);

2. 及时清理

// 组件卸载时记得断开观察
onUnmounted(() => {
  observer.disconnect();
});

总结

ResizeObserver 为我们提供了一个强大而优雅的 API,用于处理元素尺寸变化的监测需求。它不仅性能优异,使用简单,还能帮助我们构建更加灵活的响应式界面。通过合理使用 ResizeObserver,我们可以轻松实现各种复杂的布局调整和交互效果,提升用户体验。

了解更多

MDN - ResizeObserver
ResizeObserver:类似于元素的 document.onresize