当页面的内容或者文字特别多的时候 我们就需要使用到锚点。但是由于element-ui没有锚点(iview,ant有)。
故自己封装一个锚点页面:
由于锚点的滚动可能和vue路由有冲突,所以我选择自定义函数来实现锚点的滚动
第一种修改路由:
(我没有参考这种方法)
// 例子,自行封装到你項目的代碼
const router = new VueRouter({
routes,
mode: 'history',
scrollBehavior (to, from, savedPosition) {
// 如果你的链接是帶 # 这种
// to.hash 就会有值(值就是链接)
// 例如 #3
if (to.hash) {
return {
// 这个是通过 to.hash 的值來找到对应的元素
// 照你的 html 來看是不用多加处理这样就可以了
// 例如你按下 #3 的链接,就會变成 querySelector('#3'),自然會找到 id = 3 的元素
selector: to.hash
}
}
}
})
第二种自定义滚动函数:
goAnchor(selector) {
var anchor = this.$el.querySelector(selector)
document.body.scrollTop = anchor.offsetTop
}
下面封装的是整个锚点页面
<template>
<div :class="float ? 'cec-anchor_float' : 'cec-anchor'" :style="{ height }">
<div :class="`anchor-header ${float ? getHeaderPosition : ''}`">
<div class="anchor-trunk" :style="{ height: getAnchorLinkHeight }">
<div class="anchor-icon" ref="anchor-icon">
<div class="anchor-icon-white"></div>
</div>
</div>
<div class="anchor-link" :style="{ height: getAnchorLinkHeight }">
<AnchorLink
:data="data"
@goAnchor="goAnchor"
:currentAnchor="currentAnchor"
></AnchorLink>
</div>
</div>
<div ref="anchor-body" class="anchor-body" @scroll="scrollEvent($event)">
<slot></slot>
</div>
</div>
</template>
<script>
import AnchorLink from "./AnchorLink.vue";
export default {
props: {
float: Boolean,
placement: {
type: String,
default: "leftTop",
},
itemHeight: {
type: Number,
default: 30,
},
data: {
type: Array,
default: () => [],
},
height: {
type: String,
default: "600px",
},
},
components: {
AnchorLink,
},
computed: {
//获取所有锚点组合的锚点高度
getAnchorLinkHeight() {
let childrenHeight = 0;
this.data.forEach((ele) => {
if (ele.children && ele.children.length > 0) {
childrenHeight += ele.children.length * 30;
}
});
return childrenHeight + 30 * this.data.length - 20 + "px";
},
//获取所有锚点组合的位置
getHeaderPosition() {
switch (this.placement) {
case "leftTop":
return "placement-left-top";
case "leftCenter":
return "placement-left-center";
case "leftBottom":
return "placement-left-bottom";
case "rightTop":
return "placement-right-top";
case "rightCenter":
return "placement-right-center";
case "rightBottom":
return "placement-right-bottom";
default:
return "";
}
},
},
watch: {
//监听数据变化,获取所有锚点(方便后面计算),获取第一个锚点的偏差
//(因为offset计算的是当前容器被卷去的高度,可能你的高度存在偏差,所以需要减去)
data: {
handler(val) {
if (val && val.length > 0) {
this.allAnchor = this.getAllAnchor(val);
//偏差
this.$nextTick(() => {
var anchor = document.querySelector(val[0].tar);
this.offset = anchor.offsetTop;
});
}
},
deep: true,
immediate: true,
},
},
data() {
return {
currentAnchor: "",
oldHeight: 0,
scorllTimer: null,
allAnchor: [],
offset: 0,
};
},
methods: {
//递归获取所有锚点的值
getAllAnchor(anchor) {
let anchorTree = [];
anchor.forEach((ele) => {
anchorTree.push({ tar: ele.tar, title: ele.title });
if (ele.children && ele.children.length > 0) {
anchorTree.push(...this.getAllAnchor(ele.children));
}
});
return anchorTree;
},
//点击锚点滚动
goAnchor(selector) {
this.currentAnchor = selector;
var anchor = document.querySelector(selector);
let scorllHeight = anchor.offsetTop;
let height = this.oldHeight;
clearInterval(this.scorllTimer);
//使用 定时器来模拟滚动 防止一下就滚动到指定位置
// 记得判断向上滚动还是下滚动
//这里不得不提一个特别强大且好玩的css属性了 scroll-behavior 下面 我会解释
if (scorllHeight > this.oldHeight) {
this.scorllTimer = setInterval(() => {
height = height + 50;
if (height >= scorllHeight) {
height = scorllHeight;
clearInterval(this.scorllTimer);
this.scorllTimer = null;
}
// 减去偏差,不然会错位
this.$refs["anchor-body"].scrollTop = height - this.offset;
}, 10);
} else {
this.scorllTimer = setInterval(() => {
height = height - 50 - this.offset;
if (height <= scorllHeight) {
height = scorllHeight;
clearInterval(this.scorllTimer);
this.scorllTimer = null;
}
this.$refs["anchor-body"].scrollTop = height - this.offset;
});
}
//锚点前面的圆圈定位
this.allAnchor.forEach((ele, i) => {
if (ele.tar === selector) {
this.$refs["anchor-icon"].style.display = "block";
this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
}
});
this.oldHeight = scorllHeight;
},
//页面滚动, 判断当前的scrollTop在哪个锚点之间,修改锚点的样式
scrollEvent() {
if (this.scorllTimer != null) {
return;
}
//当前滚动位置
let scrollTop = this.$refs["anchor-body"].scrollTop;
this.allAnchor.forEach((ele, i) => {
let dom = document.querySelector(ele.tar);
if (dom) {
if (
scrollTop > dom.offsetTop &&
scrollTop <= dom.offsetTop + dom.offsetHeight / 3
) {
this.currentAnchor = ele.tar;
this.$refs["anchor-icon"].style.display = "block";
this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
return;
}
}
});
},
},
};
</script>
<style lang="scss">
.cec-anchor {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-content: flex-start;
overflow: hidden;
.anchor-header {
padding: 10px;
width: 10%;
height: auto;
overflow: auto;
box-sizing: border-box;
}
.anchor-body {
width: 90%;
height: 100%;
overflow: auto;
box-sizing: border-box;
}
}
.cec-anchor_float {
position: relative;
overflow: hidden;
.anchor-header {
position: absolute;
padding: 10px;
width: 10%;
height: auto;
box-sizing: border-box;
}
.anchor-body {
width: 100%;
padding: 0 10%;
height: 100%;
overflow: auto;
box-sizing: border-box;
}
}
.anchor-header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-content: flex-start;
font-size: 14px;
.anchor-trunk {
margin-top: 10px;
width: 2px;
border-radius: 1px;
position: relative;
background-color: lightgrey;
.anchor-icon {
position: absolute;
width: 8px;
height: 8px;
border-radius: 5px;
display: none;
left: -3px;
background-color: #2db7f5;
.anchor-icon-white {
width: 4px;
height: 4px;
border-radius: 2px;
margin: 2px;
background-color: white;
}
}
}
.anchor-link {
padding-left: 5px;
.anchor-link-item {
height: 30px;
line-height: 30px;
}
}
}
.placement-left-top {
top: 10px;
left: 10px;
}
.placement-left-center {
top: 50%;
left: 10px;
transform: translateY(-50%);
}
.placement-left-bottom {
left: 10px;
bottom: 10px;
}
.placement-right-top {
top: 10px;
right: 10px;
}
.placement-right-center {
top: 50%;
right: 10px;
transform: translateY(-50%);
}
.placement-right-bottom {
bottom: 10px;
right: 10px;
}
</style>
scroll-behavior是什么东西呢?
他是css的一个属性,但是目前这个属性的兼容性不是特别好,但是他能实现滚动条的平滑滑动,所以这里提供两种方式。
这样的话,我们就可以省略定时器滚动的方法了。
//如何使用呢?
// 我们先精简一下前面滚动的方法为
goAnchor(selector) {
//将原来的scrollTimer变为现在的scorllClick 防止滚动条滚动多次触发
this.scorllClick = true;
this.currentAnchor = selector;
var anchor = document.querySelector(selector);
this.$refs["anchor-body"].scrollTop = anchor.offsetTop;
this.allAnchor.forEach((ele, i) => {
if (ele.tar === selector) {
this.$refs["anchor-icon"].style.display = "block";
this.$refs["anchor-icon"].style.top = i * 30 + 2 + "px";
}
});
this.scorllClick = false;
},
scrollEvent() {
//这里变动
if (this.scorllClick) {
return;
}
......
......
......
}
//最主要的是,在你需要滚动的容器中,你需要将css属性调整
.anchor-body {
width: 90%;
height: 100%;
overflow: auto;
box-sizing: border-box;
// 这个属性会让滚动条平滑的移动
scroll-behavior: smooth;
}
锚点链接组件,主要是实现锚点链接的递归:
<template>
<div>
<div v-for="item in data" :key="item.tar">
<div class="anchor-link-item" @click="goAnchor(item.tar)">
<a
:class="currentAnchor === item.tar ? 'active-a' : ''"
href="javascript:void(0)"
>
{{ item.title }}
</a>
</div>
<div
v-if="item.children && item.children.length > 0"
class="anchor-link-group"
>
// 递归当前组件,对应的props 及方法一定要带上,否则不生效
<AnchorLink
:data="item.children"
@goAnchor="goAnchor"
:currentAnchor="currentAnchor"
></AnchorLink>
</div>
</div>
</div>
</template>
<script>
export default {
name: "AnchorLink",
props: {
data: {
type: Array,
default: () => [],
},
currentAnchor: {
type: String,
default: "",
},
},
methods: {
goAnchor(tar) {
this.$emit("goAnchor", tar);
},
},
};
</script>
<style lang="scss" scoped>
a {
color: black;
text-decoration: none;
}
.active-a {
color: #2db7f5;
}
.anchor-link-group {
margin-left: 10px;
}
</style>
在页面中使用如下:
<template>
<Anchor :data="anchorArray" height="1000px">
<div>
<div id="ca">
<p>尘埃</p>
<pre>
有一年我们排着队奔赴衰老,扭曲着
各自斑驳的身子
时间之中,一粒粒
尘埃,恰好落在孤独的位置
你松弛的面孔仿佛正遗忘自己
清晨,你的目光
犹如地图上破损的线条
望着薄雾弥漫中
远飞的蝴蝶,爱会从遥远的记忆中
诞生
你对逝去的河流谈及你失语的喉咙
哀痛便从夏夜溢满星辰的深处流出
你将在这里,以一种
巨大的孤独面对自身的遗址
我走过去
你忽然向我描绘你清晨时分盛大的婚礼
2020.5.17
</pre
>
</div>
<div id="wt">
<p>无题</p>
<pre>
夏至前夕,风以遥远使我面临日暮
而心生咏叹
我那时依稀见得
落霞,在江口的淤雾中降生
一阵风穿过消瘦、入夜和石桥
另一阵风,穿过你
从午后走出
我便完成了下午,炽热
和两阵风之间相隔的多年哀愁
美呵,以至一个完整的暮色过来
你陪流水已走了多年
夕霞尽时
夜色空无一物,看不见来路
看不见归途
我喉间失语,如古文中再无音讯的言辞
2021.6.21
</pre
>
</div>
<div id="y">
<p>雨</p>
<pre>
——写在郑州大雨的次日夜里
夏日之中,你内心静得像一处深渊
暴雨,在远处轰鸣
也似至哀无声
静到极尽,你再次看到
远处雷声:白色吊灯犹如死人
同样是那天夜里,大雨迅速繁殖
幽暗的雨水
绵延着疼痛的子嗣
生命内外,有两种人间
我们面面相觑
——相信苦难并承认神明
直至某一刻,突然忆及二十年前
那时我在暴雨之中迷失
对于恐惧
对于内心执意要彻底的感受,我如何能
如何能
除哭泣以外的方式
毫无保留地向你尽数表达出来?
2021.7.23
</pre
>
</div>
<div id="time">
<p>时间</p>
<pre>
夜深,深如一句你去年未能说完的话
像步入一条枯瘦而狭长的路
没有尽头
所以,那时的夜色
正等我望着
所以,当我望着那个地方时
会感到寂静,且遥远
我会时常想着
夏日应该会下几场雨,但是来不及
一一细数,秋天也要过去了
于是,我就在想着
在更久远的一场大雨中,那时
我在什么地方
所以,在时间之中
我望着夜色有太多遗憾。所以,我的遗憾
比隐于海底的沟壑更深,比时间
更深——
2020.10.24
</pre
>
</div>
<div id="rq">
<p>入秋</p>
<pre>
入秋如被弃置,像一个没有面孔的日子
是一阵阵秋高的风
还是我内心的夜色,过于深远
我在床上微微侧着,想象你也在侧着
眼睛像乍醒的瞬间
突然看到他省流经此地的河流
那么遥远
那么悲伤,没有尽头
阳光照着我喉间的词,我心中有话
却不能说出
清晨,我内心有囤积了数年的尘埃
有潦草而等待风干的笔迹
有如书中等待翻过的悲痛一页:风在窗外
却不进来
2020.9.4
</pre
>
</div>
</div>
</Anchor>
</template>
<script>
import Anchor from "./components/anchor.vue";
export default {
components: {
Anchor,
},
data() {
return {
anchorArray: [
{ tar: "#ca", title: "尘埃" },
{ tar: "#wt", title: "无题" },
{ tar: "#y", title: "雨" },
{ tar: "#time", title: "时间" },
{ tar: "#rq", title: "入秋" },
],
};
},
};
</script>
<style lang="scss" scoped>
p {
color: red;
font-size: 20px;
margin: 0;
padding: 0;
}
</style>
实现效果: