目录
一、最常见做法:通过 ref 获取元素
Vue 3 写法
Vue 2 写法
二、获取元素位置与宽高的几种方式
1. 获取相对于视口的位置和实际渲染宽高
适合场景
2. 获取布局宽高
特点
3. 获取内容区可视宽高
特点
4. 获取内容总宽高
适合场景
5. 获取相对父级的位置
三、如果要获取“相对于整个页面”的位置
Vue 3 示例
四、为什么在 Vue 中要用 nextTick?
示例
面试表达
五、Vue 2 中怎么拿?
示例
六、实际项目里常见场景
1. 获取元素位置做吸顶
2. 判断元素是否进入可视区
3. 获取容器宽度做图表自适应
4. 判断内容是否溢出
七、如果是组件渲染后获取子组件内部元素怎么办?
方式1:子组件自己暴露方法
子组件
父组件
方式2:直接把 DOM ref 暴露出去
八、如果元素尺寸会变化,怎么实时监听?
Vue 3 示例
适合场景
九、如果是列表循环出来的元素怎么获取?
Vue 3 中可以函数式绑定
十、面试中怎么回答更好?
标准回答
十一、可直接背的精简面试版
十二、简洁代码模板
Vue 3 通用模板
十三、一句话总结
在 Vue 里获取页面元素位置和宽高,本质上还是操作 DOM,和原生 JS 一样,核心还是这些 API:
getBoundingClientRect()offsetWidth / offsetHeightclientWidth / clientHeightscrollWidth / scrollHeightoffsetTop / offsetLeft
但在 Vue 里有一个关键点:
必须等 DOM 渲染完成后再去拿。
因为 Vue 是响应式更新的,数据变了不代表 DOM 立刻更新,所以如果你拿元素尺寸,通常要放在:
mountedonMountednextTick
里。
一、最常见做法:通过ref获取元素
在 Vue 项目中,一般不会直接到处document.querySelector,更推荐用ref。
Vue 3 写法
<template> <div ref="boxRef" class="box">内容</div> </template> <script setup> import { ref, onMounted } from 'vue' const boxRef = ref(null) onMounted(() => { const el = boxRef.value const rect = el.getBoundingClientRect() console.log('相对视口 top:', rect.top) console.log('相对视口 left:', rect.left) console.log('宽度:', rect.width) console.log('高度:', rect.height) }) </script> <style> .box { width: 200px; height: 100px; background: lightblue; } </style>Vue 2 写法
<template> <div ref="box" class="box">内容</div> </template> <script> export default { mounted() { const el = this.$refs.box const rect = el.getBoundingClientRect() console.log('top:', rect.top) console.log('left:', rect.left) console.log('width:', rect.width) console.log('height:', rect.height) } } </script>二、获取元素位置与宽高的几种方式
1. 获取相对于视口的位置和实际渲染宽高
最推荐:
const rect = el.getBoundingClientRect() console.log(rect.top) console.log(rect.left) console.log(rect.width) console.log(rect.height)适合场景
- 吸顶
- 懒加载
- 判断元素是否进入可视区
- 弹层定位
- 页面滚动联动
2. 获取布局宽高
console.log(el.offsetWidth) console.log(el.offsetHeight)特点
包含:
- content
- padding
- border
不包含:
- margin
3. 获取内容区可视宽高
console.log(el.clientWidth) console.log(el.clientHeight)特点
包含:
- content
- padding
不包含:
- border
- 滚动条
4. 获取内容总宽高
console.log(el.scrollWidth) console.log(el.scrollHeight)适合场景
- 判断内容是否溢出
- 判断是否出现滚动条
5. 获取相对父级的位置
console.log(el.offsetTop) console.log(el.offsetLeft)注意:
这是相对于
offsetParent,不是相对于页面,也不是相对于视口。
三、如果要获取“相对于整个页面”的位置
getBoundingClientRect()拿到的是相对于视口的位置。
如果你要拿元素相对于整个文档的位置,要加滚动距离。
Vue 3 示例
<template> <div ref="boxRef" class="box">内容</div> </template> <script setup> import { ref, onMounted } from 'vue' const boxRef = ref(null) onMounted(() => { const el = boxRef.value const rect = el.getBoundingClientRect() const pageTop = rect.top + window.scrollY const pageLeft = rect.left + window.scrollX console.log('相对页面 top:', pageTop) console.log('相对页面 left:', pageLeft) }) </script>四、为什么在 Vue 中要用nextTick?
这是很重要的点。
因为 Vue 修改数据后,DOM 更新是异步批量更新的。
如果你刚改完数据立刻去拿元素尺寸,可能拿到的是旧值。
示例
<template> <div ref="boxRef" :style="{ width: width + 'px' }"></div> <button @click="changeWidth">修改宽度</button> </template> <script setup> import { ref, nextTick } from 'vue' const boxRef = ref(null) const width = ref(100) const changeWidth = async () => { width.value = 300 await nextTick() console.log('最新宽度:', boxRef.value.offsetWidth) } </script>如果不加nextTick(),有可能拿到的还是旧宽度。
面试表达
在 Vue 中获取元素宽高时,除了使用原生 DOM API 外,还要注意时机问题。
一般首次获取放在mounted/onMounted,如果是数据更新后再获取,需要等nextTick,确保 DOM 已经完成更新。
五、Vue 2 中怎么拿?
Vue 2 一般通过this.$refs。
示例
<template> <div> <div ref="box" :style="{ width: width + 'px' }"></div> <button @click="changeWidth">修改宽度</button> </div> </template> <script> export default { data() { return { width: 100 } }, methods: { changeWidth() { this.width = 300 this.$nextTick(() => { console.log(this.$refs.box.offsetWidth) }) } }, mounted() { console.log(this.$refs.box.offsetWidth) } } </script>六、实际项目里常见场景
1. 获取元素位置做吸顶
const rect = el.getBoundingClientRect() if (rect.top <= 0) { console.log('元素到顶部了') }2. 判断元素是否进入可视区
const rect = el.getBoundingClientRect() const inView = rect.top < window.innerHeight && rect.bottom > 03. 获取容器宽度做图表自适应
const width = containerRef.value.clientWidth chart.resize({ width })4. 判断内容是否溢出
const el = boxRef.value const isOverflow = el.scrollWidth > el.clientWidth console.log(isOverflow)七、如果是组件渲染后获取子组件内部元素怎么办?
如果元素在子组件里,通常有两种方式。
方式1:子组件自己暴露方法
子组件
<template> <div ref="innerRef" class="inner"></div> </template> <script setup> import { ref } from 'vue' const innerRef = ref(null) const getRect = () => { return innerRef.value.getBoundingClientRect() } defineExpose({ getRect }) </script>父组件
<template> <Child ref="childRef" /> </template> <script setup> import { ref, onMounted } from 'vue' import Child from './Child.vue' const childRef = ref(null) onMounted(() => { const rect = childRef.value.getRect() console.log(rect) }) </script>方式2:直接把 DOM ref 暴露出去
也可以,但一般不如暴露方法更好。
八、如果元素尺寸会变化,怎么实时监听?
如果你不是只拿一次,而是希望尺寸变化时自动更新,可以用ResizeObserver。
Vue 3 示例
<template> <div ref="boxRef" class="box">内容</div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' const boxRef = ref(null) let observer = null onMounted(() => { observer = new ResizeObserver((entries) => { for (const entry of entries) { console.log('宽度变化:', entry.contentRect.width) console.log('高度变化:', entry.contentRect.height) } }) observer.observe(boxRef.value) }) onBeforeUnmount(() => { observer && observer.disconnect() }) </script>适合场景
- 图表容器自适应
- 弹窗内容高度变化
- 响应式布局监听
- 表格列宽变化
九、如果是列表循环出来的元素怎么获取?
在 Vue 里循环元素时,也可以配合ref。
Vue 3 中可以函数式绑定
<template> <div v-for="(item, index) in list" :key="item.id" :ref="el => setItemRef(el, index)" > {{ item.name }} </div> </template> <script setup> import { ref, onMounted } from 'vue' const list = ref([ { id: 1, name: 'A' }, { id: 2, name: 'B' } ]) const itemRefs = ref([]) const setItemRef = (el, index) => { if (el) itemRefs.value[index] = el } onMounted(() => { itemRefs.value.forEach(el => { console.log(el.getBoundingClientRect()) }) }) </script>十、面试中怎么回答更好?
如果面试问:
在 Vue 项目中怎么获取页面元素位置与宽高?
你不要只回答“用 ref”。
更好的回答应该是:
标准回答
在 Vue 项目里获取元素位置和宽高,本质上还是调用原生 DOM API,比如
getBoundingClientRect()、offsetWidth、clientHeight等。
只是 Vue 里要注意获取时机,通常会通过ref拿到 DOM 节点,然后在mounted/onMounted里获取;如果是响应式数据更新后再获取,需要等nextTick,确保 DOM 已经更新完成。如果我要获取元素相对于视口的位置和渲染后的宽高,我会优先用
getBoundingClientRect();
如果是获取布局宽高,可以用offsetWidth/offsetHeight;
如果是获取内容区域大小,可以用clientWidth/clientHeight。在实际项目中,如果元素尺寸会动态变化,我还会结合
ResizeObserver做监听。
十一、可直接背的精简面试版
在 Vue 中获取页面元素位置和宽高,一般会先通过
ref获取 DOM 元素,再结合原生 DOM API 获取。
比如:
getBoundingClientRect()获取相对于视口的位置和宽高offsetWidth/offsetHeight获取包含 border 的布局尺寸clientWidth/clientHeight获取内容区加 padding 的尺寸需要注意的是,Vue 的 DOM 更新是异步的,所以首次获取一般放在
mounted/onMounted,如果是数据更新后获取,要配合nextTick。
如果元素大小会变化,还可以使用ResizeObserver监听。
十二、简洁代码模板
Vue 3 通用模板
<template> <div ref="elRef" class="box">内容</div> </template> <script setup> import { ref, onMounted, nextTick } from 'vue' const elRef = ref(null) onMounted(() => { const el = elRef.value console.log('offsetWidth:', el.offsetWidth) console.log('clientWidth:', el.clientWidth) const rect = el.getBoundingClientRect() console.log('top:', rect.top) console.log('left:', rect.left) console.log('width:', rect.width) console.log('height:', rect.height) }) // 数据更新后获取 async function getLatestRect() { await nextTick() const rect = elRef.value.getBoundingClientRect() console.log(rect) } </script>十三、一句话总结
在 Vue 项目中获取元素位置和宽高,核心是
ref + DOM API,关键是选对生命周期和在更新后使用nextTick。