Skip to page content

useElementSize 组合式 API
v2.34+

useElementSize() composable 会通过一个保存 width 和 height 的响应式 elementSize 对象,来追踪某个元素(或组件)的尺寸。它底层使用 Resize Observer API,因此不涉及任何轮询。

它是 QResizeObserver 组件和 v-resize 指令在 setup 代码中的对应物,而这两者都是构建在它之上的。当你想在自己的组件、或任意元素 / 组件的 ref 上获取尺寸,又不想往模板里额外添加一个节点时,就可以使用这个 composable。

上报的尺寸是元素的外部尺寸(包含 padding 和 border),所以元素自身 padding 的变化也会被上报。

TIP

在 SSR 或 SSG 模式的服务端,该 composable 永远不会测量任何东西:在客户端接管之前,elementSize 始终保持为 { width: 0, height: 0 }。

在组件之外使用

该 composable 也可以在 setup() 之外调用:在 boot 文件、store 或普通模块中。此时没有可回退的组件根节点,也没有需要等待的挂载时机,所以你必须提供一个 target(一个元素,或指向元素的 ref / getter);此时观测会立即开始,且不会自行停止:完成后请调用 stopElementSize()。

语法

import { useTemplateRef } from 'vue'
import { useElementSize } from 'quasar'

setup () {
  const target = useTemplateRef('target') // an Element or a component

  const { elementSize, refreshElementSize, stopElementSize } = useElementSize({
    // all optional:
    target,                  // omit it to measure the component's own root element
    debounce: 0,             // milliseconds; at most one measurement per window
    disabled: false,         // pause observing
    onResize (elementSize) { // called with { width, height } on every change
      // ...
    }
  })

  // ...
}
function useElementSize(
  options?: MaybeRefOrGetter<{
    target?: MaybeRefOrGetter<
      Element | ComponentPublicInstance | null | undefined
    >
    debounce?: string | number
    disabled?: boolean
    onResize?: (elementSize: { width: number; height: number }) => void
  }>
): {
  elementSize: ShallowRef<{ width: number; height: number }>
  refreshElementSize: () => void
  stopElementSize: () => void
}

不提供 target 时,该 composable 会测量调用它的那个组件的根元素,以组件被挂载的那一刻为准。渲染为片段(fragment,即多个根节点)的组件没有可供测量的根元素,因此这种情况下必须提供一个 target。

第一次测量会在元素可用时立即进行,因此在你的组件挂载之后,elementSize 就已经保存了尺寸;onResize 也会为这次首测被调用一次,之后仅在尺寸发生变化时才会被调用。每一次变化都会给 elementSize 赋一个新对象,因此在该 ref 上的 watcher 会为每一次上报的尺寸各触发一次。

设置了 debounce 后,元素在每个这么多毫秒的时间窗口内最多只测量一次:一个持续进行的尺寸变化(拖动分隔条、对宽度做动画)会周期性地上报,而不是每一帧都上报,并且最后一次变化绝不会被漏掉。

refreshElementSize() 会立即测量元素,跳过防抖(debounce)(在 disabled 状态下或目标存在之前,它什么都不做)。你很少会用到它,因为浏览器会自行上报每一次变化。

stopElementSize() 会彻底结束观测。你同样很少会用到它,因为该 composable 会在组件销毁时自行停止。

运行时更改选项

选项可以是一个普通对象、一个 Ref 或一个 getter 函数。普通对象只读取一次。使用 Ref 或 getter 时,该 composable 会追踪选项所读取的任何响应式状态,并在该状态变化时重新应用它们,因此你无需调用任何东西去「更新」它:

  • 切换 disabled 会暂停和恢复观测(暂停期间 elementSize 会保留它最后的值,恢复时会立即测量)
  • 把 target 指向另一个元素(或让模板 ref 通过 v-if 发生变化)会跟随它并上报新元素的尺寸
  • 更改 debounce 会从下一次变化开始生效
  • 替换 onResize 会从下一次变化开始生效
import { ref } from 'vue'
import { useElementSize } from 'quasar'

setup () {
  const paused = ref(false)

  const { elementSize } = useElementSize(() => ({
    disabled: paused.value,
    debounce: paused.value ? 0 : 100
  }))

  function pause () { paused.value = true }
  function resume () { paused.value = false }

  // ...
}

示例

下面这个方框通过模板 ref 被测量:elementSize 会跟随它,而 onResize 钩子会统计尺寸被上报了多少次:

<template>
  <div class="q-pa-md">
    <q-btn
      color="primary"
      push
      label="Random height"
      @click="setRandomHeight"
    />

    <q-slider color="teal" v-model="boxWidth" :min="100" :max="300" label />

    <div
      ref="boxRef"
      :style="style"
      class="container bg-amber rounded-borders glossy q-my-md"
    />

    <div class="q-gutter-sm row items-center">
      <div>Measured:</div>
      <q-badge :label="`width: ${elementSize.width}`" />
      <q-badge :label="`height: ${elementSize.height}`" />
      <q-badge color="secondary" :label="`reports: ${reports}`" />
    </div>
  </div>
</template>

<script setup>
import { computed, ref, useTemplateRef } from 'vue'
import { useElementSize } from 'quasar'

const boxRef = useTemplateRef('boxRef')

const boxWidth = ref(200)
const boxHeight = ref(120)
const reports = ref(0)

const style = computed(() => ({
  width: boxWidth.value + 'px',
  height: boxHeight.value + 'px'
}))

const { elementSize } = useElementSize({
  target: boxRef,
  onResize() {
    reports.value++
  }
})

function setRandomHeight() {
  boxHeight.value = Math.floor(80 + Math.random() * 120)
}
</script>

<style lang="sass" scoped>
.container
  transition: height .3s
</style>

如果想改为测量组件自身的根元素,省略 target 即可:

<template>
  <div class="chart">
    <svg
      :viewBox="`0 0 ${elementSize.width} ${elementSize.height}`"
      :width="elementSize.width"
      :height="elementSize.height"
    >
      <!-- ... -->
    </svg>
  </div>
</template>

<script setup>
  import { useElementSize } from 'quasar'

  // measures this component's root element (the chart container)
  const { elementSize } = useElementSize({ debounce: 100 })
</script>