Vue实现锚点的功能

普通的静态网站加锚点的功能非常简单,只需要给锚点元素添加 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;
  }
}

标签: Vue

添加新评论