【vue】聊一聊监听Element的边界尺寸

背景

相信大家在日常的开发过程中大概率都遇到过需要监听Element尺寸变化的情况。比较常见的就是Echarts统计图了,当窗口变化大小的时候需要根据情况调整统计图的大小。下面的内容将以统计图为例来聊一聊我的处理方式。

addEventListener(‘resize’, () => {})

一开始做这类需要监听尺寸变化的需要的时候都会使用这种方式,在组件实例挂载完成后使用addEventListener('resize', () => {})来监听页面窗口的变化,然后在监听事件函数中调用Echartresize方法,在组件实例被卸载之前调用removeEventListener('resize', () => {})删除侦听器。附上代码:

<template>
  <div class="container" ref="refContainer">
    <div class="echart" ref="refEchart" />
  </div>
</template>

<script setup>
import { ref, reactive, markRaw, onMounted } from 'vue'
import * as echarts from 'echarts'

const refContainer = ref()
const refEchart = ref()
const echart = ref(null)
const list = ref([])
const option = reactive({})

const init = () => {
  // 初始化统计图
}

const resizeHandle = () => {
  if (echart.value) {
    echart.value.resize()
  }
}

onMounted(() => {
  init()
  refContainer.value.addEventListener('resize', resizeHandle)
})
onBeforeUnmount(() => {
  refContainer.value.removeEventListener('resize', resizeHandle)
})
</script>

<style scoped>
/* 省略样式 */
</style>

ResizeObserver

除了addEventListener方式外还以一种方式就是使用ResizeObserverResizeObserver接口会监视Element内容盒或边框盒或者SVGElement边界尺寸的变化。换句话来说就是可以监听到DOM元素宽高的变化。
内容盒是盒模型放置内容的部分,这意味着边框盒减去内边距和边框的宽度就是内容盒。边框盒包含内容、内边距和边框。

使用new ResizeObserver()会创建并返回一个新的ResizeObserver对象。它会有一个回调函数作为参数,每当观测的元素调整大小时,会调用该函数。这个回调函数会有两个参数:第一个参数是ResizeObserverEntry对象数组可以用于获取每个元素改变后的新尺寸。第二个参数是对ResizeObserver自身的引用。

ResizeObserverEntry
有五个属性:

  • borderBoxSize,是一个数组,数组里是新边框盒大小的对象包含两个属性blockSize:被监听的元素在块方向上的长度、inlineSize:被监听的元素在内联方向上的长度。
  • contentBoxSize,是一个数组,数组里是新内容盒大小的对象包含两个属性blockSize:被监听的元素在块方向上的长度、inlineSize:被监听的元素在内联方向上的长度。
  • contentRect,一个DOMRectReadOnly对象
  • devicePixelContentBoxSize,是一个数组,数组里是被监听元素的设备像素大小的对象。
  • target,是正在观察Element的引用。

ResizeObserver有三个方法:observe开始对指定Element的监听、unobserve()结束对指定Element的监听、disconnect()取消特定观察者目标上所有对Element的监听。

下面用代码简单实现一下:

<template>
  <div class="container" ref="refContainer">
    <div class="echart" ref="refEchart" />
  </div>
</template>

<script setup>
import { ref, reactive, markRaw, onMounted } from 'vue'
import * as echarts from 'echarts'

const refContainer = ref()
const resizeObserver = ref(null)
const refEchart = ref()
const echart = ref(null)
const list = ref([])
const option = reactive({})

const init = () => {
  // 初始化统计图
}

const resizeHandle = () => {
  if (echart.value) {
    echart.value.resize()
  }
}

onMounted(() => {
  init()
  resizeObserver.value = new ResizeObserver((entries) => {
    for (const entry of entries) {
      if (entry.target === refContainer.value) {
        resizeHandle()
      }
    }
  })
  resizeObserver.value.observe(refContainer.value)
})

onBeforeUnmount(() => {
  resizeObserver.value.unobserve(refContainer.value)
})
</script>

<style scoped>
/* 省略... */
</style>

基于ResizeObserver实现自定义指令

上面两种方式都是实现需求,但是都有相同的一个弊端:如果有很多组件里面都需要监听,那所有的组件都要写这些重复的代码。显然这样并不是我们所希望的,有没有什么方式可以不用这样做呢?当然是有。下面我就带领大家使用自定义指令的方式基于ResizeObserver来实现这个需求。

由上文介绍的ResizeObserver中知道它的回调函数返回的是一个数组,那我们就可以得出它是可以监听多个Element的。在这个基础上我们来实现一下:

const weakMap = new WeakMap()

const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const handler = weakMap.get(entry.target)
    if (handler) {
      handler()
    }
  }
})

export default {
  mounted(el, binding) {
    resizeObserver.observe(el)
    weakMap.set(el, binding.value)
  },
  unmounted(el) {
    resizeObserver.unobserve(el)
  }
}

以上代码就可以实现需求,但是并不能满足所有场景,比如有些场景可能需要Element的尺寸,我们在这个基础上优化一下。完整代码:

// resize.js
const args = ['borderBoxSize', 'contentBoxSize', 'contentRect', 'devicePixelContentBoxSize']

const weakMap = new WeakMap()

const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const value = weakMap.get(entry.target)
    if (value) {
      let { arg, handler } = value
      arg = arg || args[1]
      let params = entry[arg]
      if (arg !== args[2]) {
        const { blockSize, inlineSize } = entry[arg][0]
        params = {
          height: blockSize,
          width: inlineSize
        }
      }
      handler(params)
    }
  }
})

export default {
  mounted(el, binding) {
    const { arg, value } = binding
    if (typeof value !== 'function' ) {
      console.warn('[Directive warn]: Invalid value: validation failed for value. Must be a function.')
      return
    }
    if (arg && !args.includes(arg) ) {
      console.warn(`[Directive warn]: Invalid arg: validation failed for arg. Expected one of ${ JSON.stringify(args) }, got value "${arg}".`)
      return
    }
    resizeObserver.observe(el)
    weakMap.set(el, {arg, handler:binding.value})
  },
  unmounted(el) {
    resizeObserver.unobserve(el)
  }
}
// index.vue
<template>
  <div class="container" v-resize="resizeHandle">
    <div class="echart" ref="refEchart" />
  </div>
</template>

<script setup>
// 省略...
</script>

<style scoped>
/* 省略... */
</style>

这里使用WeakMap是它的键是弱引用的,其键必须是对象,而值可以是任意的。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 GC 回收掉。

以上就是本次分享的内容,附上源码

感谢看官看到这里,如果觉得文章不错的话,可以给小生的几个开源项目点个Star⭐!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值