Ukuran sebuah elemen, diikuti lewat ResizeObserver.
Dua hal yang dibawa dari sumbernya karena keduanya jawaban atas masalah nyata, bukan kerumitan yang bisa dibuang:
- SVG diukur lewat
getBoundingClientRect.contentBoxSizeuntuk elemen SVG dilaporkan berbeda-beda antar peramban, dan sebagiannya nol. observedengan opsiboxdibungkus try/catch. Peramban lama melempar untuk nilaiboxyang tidak dikenalnya; pengamatan tanpa opsi tetap lebih baik daripada tidak mengamati sama sekali.
Installation
pnpm dlx shadcn-vue@latest add @kliping/use-element-size Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.
Ikut terpasang sendiri: target.
Salin dulu target — masing-masing memuat sumbernya di halamannya sendiri — lalu tempel berkas ini.
ts
import { getCurrentScope, onScopeDispose, ref, watch } from 'vue'
import { getTargetElement } from '@/lib/target'
import type { BasicTarget } from '@/lib/target'
export interface ElementSize {
width: number
height: number
}
export interface UseElementSizeOptions {
/** Which box to measure. Default: `content-box`. */
box?: ResizeObserverBoxOptions
}
function sizeFromEntry(entry: ResizeObserverEntry, box: ResizeObserverBoxOptions): ElementSize {
const boxSize =
box === 'border-box'
? entry.borderBoxSize
: box === 'device-pixel-content-box'
? entry.devicePixelContentBoxSize
: entry.contentBoxSize
const sizes = boxSize
? ((Array.isArray(boxSize) ? boxSize : [boxSize]) as readonly ResizeObserverSize[])
: []
if (sizes.length) {
return {
width: sizes.reduce((n, u) => n + u.inlineSize, 0),
height: sizes.reduce((n, u) => n + u.blockSize, 0),
}
}
return { width: entry.contentRect.width, height: entry.contentRect.height }
}
export function useElementSize(
target: BasicTarget,
initialSize: ElementSize = { width: 0, height: 0 },
options: UseElementSizeOptions = {},
) {
const { box = 'content-box' } = options
const width = ref(initialSize.width)
const height = ref(initialSize.height)
let observer: ResizeObserver | undefined
const teardown = () => {
observer?.disconnect()
observer = undefined
}
const setSize = (size: ElementSize) => {
width.value = size.width
height.value = size.height
}
const stopWatch = watch(
() => getTargetElement(target),
(element) => {
teardown()
if (!element || !('getBoundingClientRect' in element)) {
setSize({ width: 0, height: 0 })
return
}
const el = element as Element
if (typeof ResizeObserver === 'undefined') {
const rect = el.getBoundingClientRect()
setSize({ width: rect.width, height: rect.height })
return
}
const svg = typeof SVGElement !== 'undefined' && el instanceof SVGElement
observer = new ResizeObserver((entries) => {
const entry = entries[0]
if (!entry) return
if (svg) {
const rect = el.getBoundingClientRect()
setSize({ width: rect.width, height: rect.height })
return
}
setSize(sizeFromEntry(entry, box))
})
try {
observer.observe(el, { box })
} catch {
observer.observe(el)
}
},
{ immediate: true, flush: 'post' },
)
const stop = () => {
teardown()
stopWatch()
}
if (getCurrentScope()) onScopeDispose(stop)
return { width, height, stop: stop }
}API
ts
export interface ElementSize {
width: number
height: number
}
export interface UseElementSizeOptions {
/** Which box to measure. Default: `content-box`. */
box?: ResizeObserverBoxOptions
}
export function useElementSize(
target: BasicTarget,
initialSize: ElementSize = { width: 0, height: 0 },
options: UseElementSizeOptions = {},
)