vue&jq 锚点&滑动自动高亮

4,407 阅读2分钟

vue实现 (2022-7-18更新)

效果图

image.png

交互重点

  • 除基本的交互效果,支持监听内容高度改变,动态计算锚点元素的offsetTop
  • 锚点元素的offsetTop计算规则,基于最外层滚动容器,非最近的非static的父元素

页面结构

image.png

实现代码

  • 绑定滚动事件
  this.scrollBox = $(this.$refs.scrollBox)

  this.scrollBox.bind('scroll', ()=>{
    if (this.isClickAnchor) {
      return
    }
    // 滚动
    this.autoLightAnchor()
  })
  • 锚点元素offsetTop值
getOffsetTop(obj) {
  let offsetTop = 0
  while (obj != this.scrollBox[0] && obj != null) {
    offsetTop += obj.offsetTop
    obj = obj.offsetParent
  }
  return offsetTop
},
  • 点击锚点
const offsetTop = this.getOffsetTop(锚点元素)
this.isClickAnchor = true
this.scrollBox.animate({ scrollTop: offsetTop }, () =>{
   // 即使同一个位置不会触发滚动,回调也会执行,**另animate过程中主动滚动页面会失效
    console.log('锚点结束')
    this.isClickAnchor = false
})
  • 滚动页面自动高亮对应锚点
autoLightAnchor() {
  if (this.offsetTopList.length < 2) { // 锚点数量
    return
  }
  const scrollTop = this.scrollBox.scrollTop()
  for (let i = 0;i < this.offsetTopList.length;i++) {
    if (scrollTop < this.offsetTopList[i]) {
      let activeIndex = i
      if (i > 0) {
        activeIndex = i - 1
      }
      this.$refs.anchor.activeIndex = activeIndex
     
      break
    } else if (i == this.offsetTopList.length - 1) {
      // * 最后一个
      this.$refs.anchor.activeIndex = i
      break
    }
  }
},
  • 监听内容height改变
// const elementResizeDetectorMaker = require('element-resize-detector')
this.erd = elementResizeDetectorMaker()
// 绑定监听内容dom
this.erd.listenTo(this.$refs.scrollBoxContent, () => {
    this.$nextTick(() => {
      this.setOffsetTopList() // 重新计算锚点的offsetTop
    })
})

// 解绑
this.erd.uninstall(this.$refs.scrollBoxContent)

js实现 (2018-10-11更新)

交互重点:

点击锚点,页面滑动到对应的位置,且锚点高亮
页面滑动时,滑动到指定位置时,对应的锚点高亮显示
锚点对应的内容块高度任意指定
特殊处理页面滑动到底部,确保最后一个锚点高亮显示
特殊需求,当某内容块为空,该内容块及对应的锚点均隐藏显示

效果图

页面结构

<div id="rollWrap">
        <div class="content">
            <h1>
                <a href="#section1" class="cur-anchor">内容1</a>&nbsp;&nbsp;
                <a href="#section2">内容2</a>&nbsp;&nbsp;
                <a href="#no">内容空</a>
                <a href="#section3">内容3</a>&nbsp;&nbsp;
            </h1>
            <div class="other"></div>
            <div id="section1" class="section">内容1</div>
            <div id="section2" class="section">内容2</div>
            <div id="no"></div>
            <div id="section3" class="section">内容3</div>
            <footer>
                底部
            </footer>
        </div>
    </div>

js源码

    var clickflag = false; //标识是否点击锚点触发页面滚动
    var offsetHs = [0]; //页面滚动,对应锚点高亮范围值
    var navs = '';
    init();
    /*事件绑定*/
    $('#rollWrap a').click(function(){
        clickflag = true;
        $(this).addClass('cur-anchor').siblings().removeClass('cur-anchor');
        $('#rollWrap').animate({
            scrollTop: $($(this).attr('href')).offset().top + $('#rollWrap').scrollTop()
        },function(){
            clickflag = false;
        });
        return false; //地址栏不显示标识符 #xx
    });
    $('#rollWrap').on('scroll',function(){
        isLightAnchor($('#rollWrap').scrollTop())
    })

    /*初始化,当内容块的内容为空,隐藏对应的内容块及锚点*/
    function init(){
        //一系列操作,得知空内容块为 #no
        $('#no').remove();
        $('a[href=#no]').remove();
        navs = $('#rollWrap a');
        handleOffsetValue();
    }

    /*页面滚动,对应的锚点(动态锚点)高亮,偏移值计算*/
    function handleOffsetValue(){
        for(var i = 0;i < navs.length;i++){
            var divID = $(navs[i]).attr('href');
            //计算偏移量,必须一进入页面调用,否则$(divID).offset().top 为偏移之后的动态变化值
            offsetHs.push($(divID).offset().top+$(divID).get(0).offsetHeight*0.8);
        }
    }

    /*页面scroll,计算是否需要高亮锚点*/
    function isLightAnchor(scrollTop){
        for(var i = 0;i < navs.length; i ++){
            if(scrollTop>=offsetHs[i]&&scrollTop<offsetHs[i+1]){
                if (!clickflag) {
                    $($('#rollWrap a')[i]).addClass('cur-anchor').siblings().removeClass('cur-anchor');
                }
            }
        }
        //设置滚动到底部,确保最后一个锚点高亮
        if(scrollTop==$('.content').get(0).offsetHeight-$('#rollWrap').height()){
            $($('#rollWrap a')[navs.length-1]).addClass('cur-anchor').siblings().removeClass('cur-anchor');
        }
    }

说明:

  • 滚动容器里的元素,其offset().top是相对于容器顶部而言的,当页面发生滚动,该元素对应的offset().top值是动态改变的,可正可负;
  • 设置某元素置于容器顶部,可设置容器的scrollTop = 当前的scrollTop+该元素的offset().top

css代码

html,body{
    height:100%;
    margin:0;
    padding:0;
}
div{
    margin: 0;
    padding: 0;
}
#rollWrap{
    height:100%;
    overflow-y: scroll;
}
.other{
    height:200px;
    background-image: url(./bg.jpg);
}
.section{
    color:black;
    font-size: 2em;
    text-align: center;
    background-size: 100% 100%;
    background-repeat: no-repeat;
}
#section1{
    height:500px;
    line-height: 500px;
    background-image: url(./bg1.jpg);
}
#section2{
    height:700px;
    line-height: 700px;
    background-image: url(./bg2.jpg);
}
#section3{
    height:200px;
    line-height: 200px;
    background-image: url(./bg3.jpg);
}
h1{
    right: 20px;
    top:0;
    position: fixed;
}
h1 a{
    color:#333;
    font-size: 16px;
    display: inline-block;
    padding: 5px;
    border: 1px solid #ccc;
}
.cur-anchor{
    background-color: #ccc;
}
footer{
    height:100px;
}