vue:元素(非页面)列表锚点定位功能

2 篇文章 0 订阅

        项目中需要做一个详情说明锚点定位功能,插件没有找到合适的,就打算手写一个。先看效果图。。。点击右边tab,左边的列表就会切换到对应的位置;鼠标在左边滚动或者拉动滚动条,右边的tab也会选中对应的位置。

 

思路

1、第一时间想到scrollIntoView,在实践的过程中发现怎么都行不通,首先就是元素中的子元素获取不到,在mounted生命周期方法中,getElementsByClassName获取父元素是可以打印出来的,但你获取长度的时候会发现子元素length为0。所以scrollIntoView指定子元素去滚动到其位置就pass掉了。

2、父元素scrollTop,随便设置了一个值之后发现是可以滚动的,于是就指定使用这个方法了。

实践

1、首先第一步就是要获取每个列表需要滚动多少高度,放到一个集合里,然后点击右边tab,滚动到对应index的高度。想要获取这个高度集合就少不了要获取每个列表元素的高度,第一个元素需要滚动高度为0,第二个元素滚动高度为第一个元素高度,第三个元素滚动高度为第一个元素高度加上第二个元素高度,以此类推。。。

2、因为列表数据listData是网络获取的,所以获取每个子元素高度我是在watch监听里面获取的,每次数据变化,滚动高度集合都会刷新。获取到滚动高度集合之后就可以做到点击右边tab,左边滚动到相应的位置了。

3、左边scrollView去联动右边tab,父元素设置可纵向滚动可以设置@scroll监听事件,根据event.srcElement.scrollTop获取当前已滚动的高度,然后根据此高度在滚动高度集合的for循环中匹配对应的index,然后设置选中。

难点

1、设置选中this.curSelectIndex = index 是放在tab点击事件里还是@scroll监听事件里?我开始是两边都设置,后来发现点击tab执行scrollTop之后@scroll监听事件也会执行,就把tab点击事件里的去掉了

2、获取滚动高度集合的时候,在做累加的过程中出现精度丢失,导致在@scroll监听事件中匹配index时有误差,最后去除小数部分解决。

3、最后几条已经到底了,再点击右边最后几条tab会出现不能选中,原因是点击tab调用的scrollTop方法之后,@scroll监听事件就会执行,这时候@scroll监听事件里的滚动高度是小于滚动高度集合里最后几条的,所以只能选中默认最接近的index。最后我在tab点击事件里做定时设置选中tab,用一个变量onScrollEventCanSpecifyIndex来管理@scroll事件里是否可以设置选中index

以上就是全部内容,完整可运行代码如下:

完整代码:

可复制直接运行

<template>
  <div class="box">
    <div id="scrollView" class="left" @scroll="onScroll">
      <p :id="'s' + index" v-for="(item,index) in listData" :key="index">{{index}}、{{item}}</p>
    </div>
    <div class="right">
      <p :class="curSelectIndex == index ? 'select' : ''" v-for="(item,index) in listData" :key="index" @click="scroll(index)">{{index}}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      listData: [],//列表数据
      networkListData: [
        '傻人有傻福,傻逼没有——鲁迅',
        '你越往人群中靠近一寸寻找存在感,你内心就多一份孤独与落寞。',
        '千万不要放纵自己,给自己找借口。对自己严格一点儿,时间长了,自律便成为一种习惯,一种生活方式,你的人格和智慧也因此变得更加完美。',
        '人生就那么短,不要为了变成别人喜欢的样子,委屈了自己。该开心时开心,想发泄就发泄。过好自己生活的第一件事,就是为自己而活。',
        '如果你不快乐,那就出去走走,世界这么大。风景很美、机会很多、人生很短,不要蜷缩在一处阴影中。',
        '如果我们质疑一个人扯谎,我们就应该伪装信任他,由于他会变得愈来愈神勇而有自信,并更斗胆地扯谎,最后会自己揭开自己的面具。',
        '当你感觉人生没那么如意,当你对自己的表现没那么满意,当你对自己爱的人或自己感到失望,当你觉得自己再也坚持不下去的时候,请记得对自己说:没关系,我们都是这样长大的。',
        '没有谁天生是属于谁的,任何人来到你身边愿意为你停下脚步,都是一件值得珍惜的事。这世上什么东西都有个保质期,没有比心存感激更好的保质方法,爱是用心,不是敷衍。',
        '善良给对了人,会对你感恩;善良给错了人,会让你寒心。心软给对了人,会对你情深;心软给错了人,会让你痛心。宽容给对了人,会对你热忱;宽容给错了人,会让你窝心。',
        '尽己力,听天命,无愧于心,不惑于情,顺势而为,随遇而安,知错就改,迷途知返,在喜欢自己的人身上用心,在不喜欢自己的人身上健忘,如此一生,甚好。',
        '我们不应被往昔的痛苦耗尽生命的火花,更不能用眼泪换取异性廉价的同情和怜悯。救赎应该通过自我,而不是别人。',
        '幸福是可以通过学习来获得的,尽管它不是我们的母语。',
        '伤痛使你更坚强,眼泪使你更勇敢,心碎使你更明智。所以,感谢过去吧,它会带给我们一个更好的未来。',
        '生命不是上帝用于捕捉你的错误的陷阱。你不会因为一个错误而成为不合格的人。生命是一场球赛,最好的球队也有丢分的记录,最差的球队也有辉煌的一天。我们的目标是让我们的人生经历更加的丰富。',
        '失败,并不是说明你差,而是提醒你该努力了!',
        '若非生活苦,谁愿弯腰做硕鼠。',
        '心软穷半生 财发狠心人 不要怕得罪人,该狠的时候就狠 人不狠,站不稳 心不狠难立足,记住心软是病,情深致命..............',
        '别总花时间给共享单车上锁,当你20岁所拥有 别人,30岁所拥有的东西,那你就知道什么是快乐',
        '光阴似箭,岁月如梭。',
        '三人行,必有我师焉!',
      ],//模拟网络数据
      curSelectIndex: 0,// 当前选中索引
      elementHeightList: [],// 每一个元素高度集合(包括margin、padding等)
      onScrollEventCanSpecifyIndex: true,//onScroll事件内是否可以指定curSelectIndex
    }
  },
  mounted() {
    this.getListData();
  },
  watch: {
    // 监听数据变化,改变元素高度集合数据
    listData() {
      this.$nextTick(() => {
        this.elementHeightList = [];
        this.listData.forEach((item, index) => {
          var height = $('#s' + index).outerHeight();
          // 去小数
          this.elementHeightList.push(Math.floor(height));
        });
      });
    }
  },
  methods: {
    getListData() {
      setTimeout(() => {
        this.listData = this.networkListData;
      }, 100);
    },

    // 滚动到指定index
    scroll(toIndex) {
      let scrollView = document.getElementById('scrollView');
      let scrollHeight = 0;
      this.elementHeightList.forEach((item, index) => {
        if (index < toIndex) {
          scrollHeight += item;
        }
      });
      scrollView.scrollTop = scrollHeight;

      // 点击tab移动的 select状态不能由onScroll监听中来改变
      this.onScrollEventCanSpecifyIndex = false;
      setTimeout(() => {
        this.curSelectIndex = toIndex ;
        this.onScrollEventCanSpecifyIndex = true;
      }, 1);
    },


    // 滚动监听
    onScroll(e){
      // 已经滚动到距离顶部距离
      let scrollTop = e.srcElement.scrollTop;

      let indexTop = 0;
      this.elementHeightList.forEach((item, index) => {
        if (scrollTop >= indexTop) {
          indexTop += item;
          if(this.onScrollEventCanSpecifyIndex){
            this.curSelectIndex = index ;
          }
        }
      });
    },
  }
}
</script>

<style lang="less" scoped>
.box {
  display: flex;
  width: 800px;
  height: 500px;
  margin: 0 auto;
  padding-top: 100px;

  .left {
    flex: 1;
    height: 500px;
    overflow-y: auto;
    border: 1px solid #dfdfdf;
    p {
      font-size: 18px;
      padding: 10px 15px;
    }
  }
  .right {
    width: 300px;
    margin-left: 50px;
    p {
      width: 70px;
      padding: 5px 15px;
      cursor: pointer;
    }
    .select {
      color: white;
      background: red;
    }
  }
}
</style>

结束语

大家如果有碰到什么问题或者有更好的解决办法欢迎评论区留言。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值