useElementSize() composable 会通过一个保存 width 和 height 的响应式 elementSize 对象,来追踪某个元素(或组件)的尺寸。它底层使用 Resize Observer API,因此不涉及任何轮询。
它是 QResizeObserver 组件和 v-resize 指令在 setup 代码中的对应物,而这两者都是构建在它之上的。当你想在自己的组件、或任意元素 / 组件的 ref 上获取尺寸,又不想往模板里额外添加一个节点时,就可以使用这个 composable。
上报的尺寸是元素的外部尺寸(包含 padding 和 border),所以元素自身 padding 的变化也会被上报。
在 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>