Vue实现锚点的功能
2019年4月26日...大约 3 分钟
普通的静态网站加锚点的功能非常简单,只需要给锚点元素添加 id ,然后将 a 标签绑定上对应的 id 即可,在 Vue 中也可以这样来实现,但是会有缺点是,Vue 是单页应用,当点击了多个锚点跳转之后,点击浏览器的后退按钮,会在这多个锚点的历史上后退,而无法实现我们想要的退回上一页的操作。
要实现的需求
有一个 Tab 栏,处在页面中间的位置,当滚动过这个位置之后,Tab 栏悬浮在视口上,当滚动经过一个个的模块的时候,对应模块的 Tab 项要高亮显示。
模块是动态加载的,也就是说模块不一定存在(就是这一问题比较麻烦,要加判断,增加了代码量)
相关代码
东西比较简单,我就不赘述了,看代码就能明白。
mounted () { // 在页面挂载后添加对滚动事件的监听
window.addEventListener('scroll', this.handleScroll);
// 离元素要有 10px 的距离,减去导航栏的 60px ,总共减 50px
this.tabBoxOffsetTop = tools.getElementToPageTop(this.$refs.tabBox) -50
this.initData()
},
destroyed () { // 在页面销毁后去掉对滚动事件的监听,写在 beforeDestroy 中并不生效
window.removeEventListener('scroll', this.handleScroll);
}
主体代码
handleScroll (e) { // 滚动监听函数
var winTop = document.documentElement.scrollTop
let topList = this.getTopList()
if (winTop >= this.tabBoxOffsetTop) {
this.fixedTab = true
} else {
this.fixedTab = false
}
this.getActiveClass(topList, winTop)
},
toAnchor (id) { // 锚点跳转函数
document.querySelector('#' + id).scrollIntoView(true);
this.fixedNavActive = id
},
getTopList () { // 获取模块高度集合,如果存在就记录高度和 id 名
let obj = {}
let topList = []
var introduceOffsetTop = tools.getElementToPageTop(document.querySelector('#introduction'))
obj.top = introduceOffsetTop
obj.class = 'introduction'
topList.push({...obj})
if (this.nodeInfo.team !== '') {
var teamOffsetTop = tools.getElementToPageTop(document.querySelector('#member'))
obj.top = teamOffsetTop
obj.class = 'member'
topList.push({...obj})
}
if (this.nodeInfo.communityPlan !== '') {
var planOffsetTop = tools.getElementToPageTop(document.querySelector('#plan'))
obj.top = planOffsetTop
obj.class = 'plan'
topList.push({...obj})
}
if (this.nodeInfo.serverConfig !== '') {
var configOffsetTop = tools.getElementToPageTop(document.querySelector('#config'))
obj.top = configOffsetTop
obj.class = 'config'
topList.push({...obj})
}
if (this.nodeInfo.estimatedCost !== '') {
var budgetOffsetTop = tools.getElementToPageTop(document.querySelector('#budget'))
obj.top = budgetOffsetTop
obj.class = 'budget'
topList.push({...obj})
}
if (this.statusList.length > 0) {
var statusOffsetTop = tools.getElementToPageTop(document.querySelector('#status-update'))
obj.top = statusOffsetTop
obj.class = 'status-update'
topList.push({...obj})
}
return topList
},
getActiveClass (topList, winTop) {
// 判断该让谁高亮显示,本函数中的 60 为项目中的导航栏高度,可将其视为 0 来理解
let count = topList.length
let index = 0
while ((index + 1) < count) {
// 判断是否在 index 所在的元素与 index + 1 所在的元素区域内
if ((topList[index].top - winTop) < 60 && (topList[index + 1].top - winTop) >= 60) {
this.fixedNavActive = topList[index].class
}
index++
}
// 对于最后一项做单独的判断
if ((topList[count - 1].top - winTop) <= 60) {
this.fixedNavActive = topList[count - 1].class
}
// 当没有滑过 Tab 栏的时候,取消高亮
if ((topList[0].top - winTop) > 60) {
this.fixedNavActive = ''
}
},
因为元素滚到锚点处的时候导航栏和悬浮 Tab 会覆盖到要显示的元素,所以在元素上面加一个不显示但是存在的元素,将 id 加在这个元素上面,设置负的 top 值来实现需求
<div class="intro-html-cont">
<div class="anchor-point" :id="currentTab"></div>
<div class="node-info-cont">
<div class="info-title">{{currentTitle}}</div>
<div class="info-content" v-html="content"></div>
</div>
</div>
.anchor-point {
position: relative;
top: -132px; /* 导航栏高度 60 + 悬浮 Tab 高度 62 + 预留显示空间 10 */
left: 0;
}
用到的工具方法,用这个方法可以更加准确的获取元素到文档顶部的距离
/*
* 获取元素距文档顶部的距离
* @param el dom节点元素
*/
getElementToPageTop(el) {
let t = el.offsetTop;
while (el = el.offsetParent) {
t += el.offsetTop;
}
return t;
}
}