You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3组合式API中v-for循环内使用getBoundingClientRect()的问题

Vue选项式API转组合式API获取元素位置问题

原选项式API代码

<template>
  <div>
    <div v-for="index in divs" :key="index">
      <div :ref="'ref_' + index" @mouseover="divDragOver($event, index)">
        This is div {{ index }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data: () => ({
    divs: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  }),
  methods: {
    divDragOver(e, i) {
      let [div] = this.$refs['ref_' + i];
      let top = div.getBoundingClientRect().top;
      console.log(top);
    },
  },
};
</script>

尝试转换的组合式API代码(无法正常运行)

<template>
  <div>
    <div v-for="index in divs" :key="index">
      <div :ref="'ref_' + index" @mouseover="divDragOver($event, index)">
        This is div {{ index }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

  const divs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  function divDragOver(e, i) {
    let itemRefs = ['ref_' + i]
    let top = itemRefs.getBoundingClientRect().top
    console.log(top)
  }
</script>

实际应用场景代码

<template>
    <li v-for="item in navigation" :key="item.name" class="px-1">
        <Menu
            as="div"
            v-slot="{ open }"
            class="relative"
            :ref="(el) => (refs[item.name] = el)"
        >
            <MenuButton
                :class="[
                    item.current ? 'bg-gray-100' : 'hover:bg-gray-100',
                    'group flex h-10 w-full items-center justify-center rounded-md p-2.5 text-standard font-semibold leading-6 text-gray-700',
                ]"
                @click="getParentPosition($event, item.name)"
            >
                <span
                    class="-mx-2.5 flex w-10 flex-none items-center justify-center"
                >
                    <component
                        :is="item.icon"
                        class="h-5 w-5 shrink-0 text-gray-400"
                        aria-hidden="true"
                    />
                </span>
            </MenuButton>
            <transition
                enter-active-class="transition ease-out duration-100"
                enter-from-class="transform opacity-0 scale-95"
                enter-to-class="transform opacity-100 scale-100"
                leave-active-class="transition ease-in duration-75"
                leave-from-class="transform opacity-100 scale-100"
                leave-to-class="transform opacity-0 scale-95"
            >
                <MenuItems
                    class="fixed left-14 z-10 mt-1.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-md ring-1 ring-gray-900/5 focus:outline-none"
                >
                    <MenuItem
                        v-for="subItem in item.children"
                        :key="subItem.name"
                        v-slot="{ active }"
                    >
                        <a
                            :href="item.href"
                            :class="[
                                active ? 'bg-gray-50' : '',
                                'block px-3 py-1 text-standard leading-6 text-gray-500',
                            ]"
                        >
                            {{ subItem.name }}
                        </a>
                    </MenuItem>
                </MenuItems>
            </transition>
        </Menu>
    </li>
</template>

<script setup>
import { ref } from 'vue'

const refs = ref([])

function getParentPosition(e, k) {
    let itemRef = refs.value[k]
    console.log(itemRef)
}
</script>

解决方案

1. 基础示例的正确转换

组合式API中,动态生成的ref需要用响应式对象/数组存储,不能直接通过字符串访问。以下是修正后的代码:

<template>
  <div>
    <div v-for="index in divs" :key="index">
      <!-- 用回调函数将元素存入响应式对象 -->
      <div :ref="el => { if (el) refs[index] = el }" @mouseover="divDragOver($event, index)">
        This is div {{ index }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const divs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 用响应式对象存储每个元素的ref
const refs = ref({})

function divDragOver(e, i) {
  const div = refs.value[i]
  if (div) {
    const top = div.getBoundingClientRect().top
    console.log(top)
  }
}
</script>

2. 实际场景的修正

实际场景中,item.name是字符串类型的键,用数组存储ref不合适,应改为响应式对象,同时注意组件卸载时的null值处理:

<template>
    <li v-for="item in navigation" :key="item.name" class="px-1">
        <Menu
            as="div"
            v-slot="{ open }"
            class="relative"
            <!-- 确保元素存在时再赋值 -->
            :ref="(el) => { if (el) refs.value[item.name] = el }"
        >
            <MenuButton
                :class="[
                    item.current ? 'bg-gray-100' : 'hover:bg-gray-100',
                    'group flex h-10 w-full items-center justify-center rounded-md p-2.5 text-standard font-semibold leading-6 text-gray-700',
                ]"
                @click="getParentPosition($event, item.name)"
            >
                <span
                    class="-mx-2.5 flex w-10 flex-none items-center justify-center"
                >
                    <component
                        :is="item.icon"
                        class="h-5 w-5 shrink-0 text-gray-400"
                        aria-hidden="true"
                    />
                </span>
            </MenuButton>
            <!-- 省略其他代码 -->
        </Menu>
    </li>
</template>

<script setup>
import { ref } from 'vue'

// 改为响应式对象存储字符串键对应的元素
const refs = ref({})

function getParentPosition(e, k) {
    const itemRef = refs.value[k]
    if (itemRef) {
        // 获取元素位置信息
        const position = itemRef.getBoundingClientRect()
        console.log(position)
    }
}
</script>

内容的提问来源于stack exchange,提问作者ToxifiedM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 12:16:11