本文共五部分,问题出现场景、定位问题、思考原因、问题解决、总结。 定位到ref获取到Vue 组件实例的代理(Proxy)对象问题。
1. 问题出现场景
vue3使用Splitpanes组件,实现可拖拽分割窗口功能时,需要在Pane组件上,实现滚动到底部效果。
在开发过程中发现,在pane组件上使用ref获取组件属性,获取不到scrollHeight,值为undefined。
html代码如下:
html
<Splitpanes class="flex flex-row h-full default-theme">
<Pane
v-for="(item, index) in models"
ref="scrollRef"
:key="index"
:class="[isMobile ? 'p-2' : 'p-4']"
class="h-full w-1/2 m-auto dark:bg-[#101014] overflow-hidden overflow-y-auto"
>
<div id="image-wrapper" class="relative">
<Card
:src-url="item.avatar"
:avatar-type="item.type" :data-sources="modelChat(item.type, +uuid)" :loading="loading"
@scroll-update="scrollUpdate" @handle-stop="handleStop" @handle-delete="handleDelete"
@on-regenerate="onRegenerate"
/>
</div>
</Pane>
</Splitpanes>
滑轮滚动到底部js代码如下:
js
const scrollToBottom = async () => {
// 等待下一个 DOM 更新周期,确保 DOM 已经渲染完成
await nextTick()
console.warn('scrollRef:', scrollRef.value)
// 处理多个滚动容器的情况(数组形式)
if (scrollRef.value && Array.isArray(scrollRef.value)) {
scrollRef.value.forEach((item) => {
// 设置滚动位置为滚动容器的总高度,即滚动到底部
console.warn('scrollHeight:', item.scrollHeight)
item.scrollTop = item.scrollHeight
})
}
// 处理单个滚动容器的情况
else if (scrollRef.value) {
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
2.定位问题
使用浏览器打印节点,查看属性:

可以看出,ref获取到Vue 组件实例的代理(Proxy)对象,【在 Vue 3 中,组件实例被包装在 Proxy 中以实现响应式】, 每个组件实例上都有一些属性,例如:
$i18n
:国际化相关的对象$el
:组件根 DOM 元素的引用(目前是省略状态,可能需要展开才能看到具体值)$attrs
:组件的属性$props
:组件的 props$emit
:触发事件的方法- 以及一些其他 Vue 内部属性和方法
实例上没有scrollHeight属性。
3. 思考原因
scrollHeight
是 DOM 元素的属性,而不是 Vue 组件实例的属性。当你通过 ref 获取第三方组件时,你得到的是组件实例,而不是底层的 DOM 元素。
对比说明:
属性 | 所属对象 | 说明 |
---|---|---|
scrollTop |
组件实例/ DOM 元素 | 组件可能暴露了这个属性 |
scrollHeight |
DOM 元素 | 原生 DOM 属性,不会自动暴露 |
4. 问题解决
对ref获取的节点,取$el属性,获取scrollHeight值,所以修改js代码为:
js
const scrollRef = ref<ComponentPublicInstance>()
/**
* 滚动到底部
* 将滚动条滚动到容器的最底部
* 适用于聊天场景,当有新消息时自动滚动到底部
*/
const scrollToBottom = async () => {
// 等待下一个 DOM 更新周期,确保 DOM 已经渲染完成
await nextTick()
// 处理多个滚动容器的情况(数组形式)
if (scrollRef.value && Array.isArray(scrollRef.value)) {
scrollRef.value.forEach((item) => {
// 设置滚动位置为滚动容器的总高度,即滚动到底部
if (item.$el) {
item.$el.scrollTop = item.$el.scrollHeight
}
})
}
// 处理单个滚动容器的情况
else if (scrollRef.value && scrollRef.value.$el) {
const documentElement = scrollRef.value.$el
documentElement.scrollTop = documentElement.scrollHeight
}
}
5. 总结
使用vue3提供的方法时,确定函数返回的对象类型很重要。
以上是手动写的hooks函数,也可以使用函数 | VueUse 工具库, vue成熟的hooks库,里面实现的scroll相关函数。