1.正常联动锚点
效果图:
以下vue2代码直接复制使用
<template>
<div style="height: 100%;">
<div style="display: flex;height: 100%;">
<div style="width: 200px;border: 1px solid pink;">
<div v-for="(item, index) in titleList" :key="index" style="margin-top: 10px;cursor: pointer;"
:style="{ backgroundColor: activeAnchor === item.dataKey ? 'cyan' : '#fff' }"
@click="() => scrollToAnchor(item.dataKey)">
{{ item.label }}
</div>
</div>
<!-- 这个大集合需要设置滚动 overflow: hidden;overflow-y: auto; -->
<div ref="scrollContainer" id="scrollContainer" style="flex: 1;overflow: hidden;overflow-y: auto;" @scroll="handleScroll">
<div ref="title1" id="title1" style="height: 200px; background-color: #c1f0de;">1</div>
<div ref="title2" id="title2" style="height: 600px; background-color: #c1f0de;margin-top: 10px;">2</div>
<div ref="title3" id="title3" style="height: 500px; background-color: #c1f0de;margin-top: 10px;">3</div>
<div ref="title4" id="title4" style="height: 200px; background-color: #c1f0de;margin-top: 10px;">4</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeAnchor: 'title1',//当前选中锚点
isScrollingToAnchor: false, //是否滚动中
titleList: [
{ dataKey: 'title1', label: '标题1' },
{ dataKey: 'title2', label: '标题2' },
{ dataKey: 'title3', label: '标题3' },
{ dataKey: 'title4', label: '标题4' },
]
}
},
methods: {
// 滚动监听 当前所展示的锚点
handleScroll() {
// 如果正在滚动到锚点,则不再触发新的滚动操作
if (this.isScrollingToAnchor) return false
const containerElement = document.getElementById('scrollContainer')
if (!containerElement) return
if (this.titleList && this.titleList.length) {
for (let i = 0; i < this.titleList.length; i++) {
const dataKey = this.titleList[i].dataKey
console.log('dataKey', dataKey, containerElement)
const anchorElement = document.getElementById(dataKey)
if (!anchorElement || anchorElement.length < 0) return
// getBoundingClientRect()方法返回一个DOMRect对象,该对象提供了元素的大小及其相对于视口的位置信息。这个DOMRect对象包含了元素的位置、宽度、高度和其他相关信息
const containerRect = containerElement.getBoundingClientRect()
// const anchorRect = anchorElement[0].getBoundingClientRect()
const anchorRect = anchorElement.getBoundingClientRect()
console.log(containerRect, anchorRect, dataKey)
// 判断锚点是否在滚动容器的可见区域内
const isInViewport =
anchorRect.top >= containerRect.top &&
anchorRect.bottom <= containerRect.bottom
if (isInViewport) {
this.activeAnchor = dataKey
return false
}
}
}
},
// 滚动锚点
scrollToAnchor(dataKey) {
const anchorElement = document.getElementById(dataKey)
// 使用原生JavaScript滚动到锚点位置
if (anchorElement) {
this.isScrollingToAnchor = true
anchorElement.scrollIntoView({
behavior: 'smooth', // 添加平滑滚动效果,可选
block: 'start' // 控制垂直方向的对齐方式,可选值有 'start', 'center', 'end', 'nearest'
})
this.activeAnchor = dataKey
// 滚动完成后将标志位复位
setTimeout(() => {
this.isScrollingToAnchor = false
}, 3000) // 3000 毫秒是滚动完成的估计时间,可以根据实际情况调整
}
}
},
}
</script>
<style></style>
2.div使用prop自定义属性锚点跳转:
效果图:
以下vue3代码可直接复制
<template>
<div>
<input v-model="targetProp" type="number" placeholder="Enter a number between 1 and 10" />
<button @click="scrollToDiv">Scroll to Div</button>
<div class="container">
<!-- <div v-for="number in numbers" :key="number" :id="'box-' + number" :prop="number" class="box">
Div {{ number }}
</div> -->
<div prop="1" class="box">1</div>
<div prop="2" class="box">2</div>
<div prop="3" class="box">3</div>
<div prop="4" class="box">4</div>
<div prop="5" class="box">5</div>
<div prop="6" class="box">6</div>
<div prop="7" class="box">7</div>
<div prop="8" class="box">8</div>
<div prop="9" class="box">9</div>
<div prop="10" class="box">10</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const numbers = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const targetProp = ref('');
const scrollToDiv = () => {
const targetValue = targetProp.value;
// const targetDiv = document.getElementById(`box-${targetValue}`) // 同等上方注释的 使用id锚点跳转
const targetDiv = document.querySelector(`[prop="${targetValue}"]`) // 使用prop自定义属性跳转
if (targetDiv) {
targetDiv.scrollIntoView({ behavior: 'smooth' });
}
};
</script>
<style>
.container {
height: 500px;
overflow-y: scroll;
border: 1px solid #ccc;
}
.box {
width: 200px;
height: 200px;
margin: 10px;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #ddd;
}
</style>
3.通过前端组件库 Vue 组件实例上的自定义属性跳转
例如通过el-form-item组件标签上的prop属性来锚点跳转
效果图:
重点介绍:
1.由于 el.getAttribute(‘prop’) 得到的值是 null,这是因为 prop 属性在 el-form-item 中是通过 Vue 绑定的,不会直接在 DOM 中作为属性存在。我们需要通过 Vue 的实例来访问这些绑定的属性。__vueParentComponent 和 ctx 就是其中的两个内部属性。
以下vue3代码可直接复制:
<template>
<div>
<el-input v-model="targetProp" placeholder="输入el-form-item的prop值" clearable />
<el-button type="primary" @click="scrollToFormItem">Scroll to Form Item</el-button>
<div style="height: 1000px;border: 1px solid red;">1</div>
<el-row>
<el-col :span="12">
<el-form-item :label="'厂商负责人'" prop="manufactureOwner">
<el-input placeholder="请输入" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="'厂商'" prop="manufactureName">
<el-input placeholder="请输入" clearable />
</el-form-item>
</el-col>
<el-col :span="24">
<div style="height: 1000px;border: 1px solid red;">1</div>
</el-col>
<el-col :span="12">
<el-form-item :label="'厂商负责人电话'" prop="manufactureOwnerPhone">
<span class="ml">1223</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="'厂商工作人员'" prop="manufactureWorker">
<span class="ml">444</span>
</el-form-item>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElInput, ElButton, ElRow, ElCol, ElFormItem } from 'element-plus';
import 'element-plus/dist/index.css';
const targetProp = ref('');
const formItems = ref([]);
onMounted(() => {
formItems.value = document.querySelectorAll('.el-form-item');
});
const scrollToFormItem = () => {
const targetElement = Array.from(formItems.value).find((el) => {
console.log(el, el.__vueParentComponent, el.__vueParentComponent.ctx);
const instance = el.__vueParentComponent.ctx;
return instance.prop === targetProp.value;
});
console.log(targetElement);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
};
</script>
<style>
.ml {
margin-left: 10px;
}
</style>
4.Vue 组件实例和内部属性
在 Vue 中,每个组件实例都有一些内部属性和方法,这些属性和方法不在 Vue 的文档中公开,但它们是 Vue 内部工作的一部分。__vueParentComponent 和 ctx 就是其中的两个内部属性。
__vueParentComponent:
-
作用:
__vueParentComponent
是 Vue 组件实例中的一个内部属性,指向该组件的父组件实例。 -
用途: 它主要用于内部组件树的管理,使 Vue 能够正确地管理父子组件之间的关系。
ctx:
- 作用:
ctx
是 Vue 组件实例中的一个内部属性,代表该组件的上下文(context)。 - 用途:
ctx
包含了组件的所有公开属性和方法,包括props、data、computed、methods
等。
代码解释
以下是关键代码:
const instance = el.__vueParentComponent.ctx;
- el: 这是一个 HTML 元素,通常是通过
- document.querySelectorAll 或其他 DOM 查询方法获得的。
- el.__vueParentComponent: 这是指向包含 el 的 Vue 组件实例。
- el.__vueParentComponent.ctx: 这是指向该 Vue 组件实例的上下文,在这个上下文中你可以访问该组件的所有公开属性和方法,包括 props。
5.如何使用 __vueParentComponent 和 ctx 实现功能
我们需要通过 prop
属性值找到对应的 <el-form-item>
,并滚动到该元素的位置。因为 prop
属性在 DOM 中不可见,所以我们需要访问 Vue 组件实例来获取这个值。
具体实现如下:
const targetElement = Array.from(formItems.value).find((el) => {
const instance = el.__vueParentComponent.ctx;
return instance.prop === targetProp.value;
});
- Array.from(formItems.value): 将 formItems 中的 NodeList 转换为数组,以便可以使用 find 方法。
- el.__vueParentComponent.ctx: 访问包含该元素的 Vue 组件实例的上下文。
- instance.prop: 获取该组件实例的 prop 属性值。
- return instance.prop === targetProp.value: 查找 prop 属性值等于用户输入的 targetProp.value 的组件实例。