vue上实现左右关联滚动

先看效果:
在这里插入图片描述

代码:

<template>
  <div class="container">
    <!-- 左侧fixed导航区域 -->
    <div class="left">
      <div
        v-for="item in leftList"
        :key="item.id"
        class="left_item"
        :class="item.id == selectedId ? 'selected' : ''"
        @click="leftItemClick(item.id)"
      >
        {{ item.title }}
      </div>
    </div>

    <!-- 右侧内容区域 -->
    <div class="right">
      <div v-for="item in rightList" :key="item.id" class="right_item">
        {{ item.content }}
      </div>
    </div>
  </div>
</template>

<script>
import scrollUtil from "@/common/js/scrollUtil.js";

export default {
  data() {
    return {
      leftList: [],
      rightList: [],
      selectedId: 1,
      offsetTopArr: [],
    };
  },
  created() {
    this.initData();
  },
  mounted() {
    // 监听滚动事件
    window.addEventListener("scroll", this.onScroll);

    this.getOffsetTopArr();
  },
  destroyed() {
    // 移除监听器
    window.removeEventListener("scroll", this.onScroll);
  },
  methods: {
    //获取右侧所有item的offsetTop
    getOffsetTopArr() {
      this.$nextTick(() => {
        let rightListItems = document.querySelectorAll(".right .right_item");
        if (rightListItems && rightListItems.length > 0) {
          rightListItems.forEach((item) => {
            this.offsetTopArr.push(item.offsetTop);
          });
          console.log(this.offsetTopArr);
        }
      });
    },
    //页面滚动监听
    onScroll() {
      // 获取当前文档流的 scrollTop
      const scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop;
      console.log("scrollTop=" + scrollTop);
      for (let i = 0; i < this.offsetTopArr.length; i++) {
        if (scrollTop >= this.offsetTopArr[i]) {
          this.selectedId = this.rightList[i].parentId;
        }
        // todo 优化 => 改成小于等于,加break 少循环
      }
    },
    //左侧item点击事件
    leftItemClick(id) {
      this.selectedId = id;

      let index = 0;
      for (let i = 0; i < this.rightList.length; i++) {
        if (this.rightList[i].parentId == id) {
          index = i;
          break;
        }
      }

      const targetOffsetTop = this.offsetTopArr[index];
      scrollUtil.scrollTo(targetOffsetTop);
    },
    //初始化数据源
    initData() {
      for (let index = 1; index < 10; index++) {
        for (let i = 1; i < 5; i++) {
          this.rightList.push({
            id: index + "-" + i,
            parentId: index,
            content: "content-" + index,
          });
        }
        this.leftList.push({
          id: index,
          title: "title-" + index,
        });
      }
    },
  },
};
</script>

<style lang="less" scoped>
.container {
  position: relative;
  min-height: 100vh;
  background: #fff;

  .left {
    position: fixed;
    width: 120px;
    height: 100%;
    min-height: 100vh;
    overflow: auto;
    float: left;
    background: #f2f2f2;

    .left_item {
      width: 100%;
      height: 60px;
      text-align: center;
      line-height: 60px;
    }

    .selected {
      background: #fff;
      font-weight: bold;
      color: #07c160;
    }
  }

  .right {
    margin-left: 120px;
    width: calc(100vw - 120px);
    overflow: auto;

    .right_item {
      width: 100%;
      height: 200px;
      text-align: center;
      line-height: 200px;
      font-size: 24px;
      border-bottom: 1px solid #ccc;
    }
  }
}
</style>

其中scrollUtil.js:

const scrollUtil = {
  /**
   * 滚动到目标offsetTop
   * @param {*} targetOffsetTop 目标offsetTop
   */
  scrollTo: function (targetOffsetTop) {
    // 当前offsetTop
    let scrollTop =
      document.documentElement.scrollTop || document.body.scrollTop;

    // 定义一次跳50个像素,数字越大跳得越快,但是会有掉帧得感觉
    const STEP = 50;

    // 判断是往下滑还是往上滑
    if (scrollTop > targetOffsetTop) {
      // 往上滑
      smoothUp();
    } else {
      // 往下滑
      smoothDown();
    }

    // 定义往下滑函数
    function smoothDown() {
      // 如果当前 scrollTop 小于 targetOffsetTop 说明视口还没滑到指定位置
      if (scrollTop < targetOffsetTop) {
        // 如果和目标相差距离大于等于 STEP 就跳 STEP
        // 否则直接跳到目标点,目标是为了防止跳过了。
        if (targetOffsetTop - scrollTop >= STEP) {
          scrollTop += STEP;
        } else {
          scrollTop = targetOffsetTop;
        }
        document.body.scrollTop = scrollTop;
        document.documentElement.scrollTop = scrollTop;
        // 屏幕在绘制下一帧时会回调传给 requestAnimationFrame 的函数
        // 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高
        requestAnimationFrame(smoothDown);
      }
    }

    // 定义往上滑函数
    function smoothUp() {
      if (scrollTop > targetOffsetTop) {
        if (scrollTop - targetOffsetTop >= STEP) {
          scrollTop -= STEP;
        } else {
          scrollTop = targetOffsetTop;
        }
        document.body.scrollTop = scrollTop;
        document.documentElement.scrollTop = scrollTop;
        requestAnimationFrame(smoothUp);
      }
    }
  },
};

export default scrollUtil;


注意:样式要加box-sizing配置,消除border的影响,否则左边index根据页面滚动定位会不准确
在这里插入图片描述

没加box-sizing,offsetTopArr的打印结果:
在这里插入图片描述

加了box-sizing,offsetTopArr的打印结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值