2021-08-24

滚动导航组件实现

1.支持点击对应的索引,页面自动滚动到对应内容区域。
2.支持鼠标滚轮滚动左侧内容区域后,对应索引变为激活状态。

一.首先明白dom各高度几个概念

网页(元素)可见区域高:document.body.clientHeight
网页(元素)正文全文高:document.body.scrollHeight
网页(元素)被卷去的高:document.body.scrollTop
网页(元素)可见区域高(包括padding、border、水平滚动条):document.body.offsetHeight
屏幕分辨率高:window.screen.height
offsetTop应该是当前元素顶部距离最近的"设置了position属性的"父元素的距离,如果没有设置的话就是针对于body顶部的距离了
二、获取元素位置的方法

getPositions() {
        let last = 0;
        console.log("this.usedList",this.usedList)
        //[{target: "#Step1",text: "基础管理工作哦"},{target: "#Step2",text: "医保管理工作"}]
        this.positions = this.usedList.map((n, index) => {
          let dom = document.querySelector(n.target);
          let style = document.defaultView.getComputedStyle(dom);
          let marginTop = parseInt(style["marginTop"].replace("px", ""), 10);
          let marginBottom = parseInt(
            style["marginBottom"].replace("px", ""),
            10
          );
          //注意:是累加哦,offsetHeight就是高度 + padding + border
          last += dom.offsetHeight + marginTop + marginBottom;
          return last;
        });
        console.log("this.positions",this.positions)
        //[260,435] 获取位置
        this.areapositions = this.positions.map((n, index) => {
          let r = [];
          if (index === 0) {
            r[0] = 0;
            r[1] = n;
          } else {
            r[0] = this.positions[index - 1];
            r[1] = n;
          }
          return r;
        });
        console.log("this.areapositions",this.areapositions)
        // [[0, 260],[260, 435]] 获取位置
      }

三、滚动监听并设置激活的索引

wheel(e) {
        //先获取位置
        this.getPositions();
        //元素被卷去的高
        let scrollTop = this.containerDom.scrollTop;
        //网页可见区域高
        let clientHeight = this.containerDom.clientHeight;
        //网页正文全文高
        let scrollHeight = this.containerDom.scrollHeight;
        //如果被卷去高度为0,说明当前第一个元素激活
        if (scrollTop === 0) {
          this.nowIndex = 0;
        }
        //如果被卷去的高度 = 总高度 - 可见区域的高,说明当前为最后一个元素
        else if (scrollTop === scrollHeight - clientHeight) {
          this.nowIndex = this.positions.length - 1;
        }
        //
        else {
          _.forEach(this.areapositions, (item, index) => {
            if (scrollTop >= item[0] && scrollTop <= item[1]) {
              this.nowIndex = index;
              return false;
            }
          });
        }
      }

四、点击索引滚动到指定位置

onChange(item, index) {
        this.getPositions();
        const go = () => {
          this.nowIndex = index;
          let toElement = document.querySelector(this.list[index].target);
          toElement.scrollIntoView({
            behavior: "smooth"
          });
        };
        let flag = true;
        //不考虑 clickable
        if (item.clickable) {
          flag = item.clickable(item, index);
        }
        if (flag) {
          go();
        }
      }

五、完整代码

<template>
  <div class="anchorNavWarpper">
    <el-scrollbar>
      <div
        v-for="(item, index) in list"
        :key="index"
        class="navItem"
        :class="classable(item, index)"
        @click="onChange(item, index)"
      >
        <el-row class="kpiRow">
          <el-col :span="8">
            <el-tooltip v-if="item.text.length > 6" class="item" effect="dark" :content="item.text" placement="top">
              <span>{{item.text.substr(0,5)}}...</span>
            </el-tooltip>
            <span v-else>{{item.text}}</span>
          </el-col>
          <el-col :span="10">{{ item.length }}</el-col>
          <el-col :span="6">{{ item.sco }}</el-col>
        </el-row>
      </div>
    </el-scrollbar>
  </div>
</template>

<script>
  import _ from "lodash";
  export default {
    name: "anchorTable",
    props: {
      list: {
        type: Array,
        default: () => {
          return [];
        }
      },
      //滚动区域的容器id,在其内部包含一个 el-scrollBar。后面拼.el-scrollbar__wrap 来获取滚动容器
      container: {
        type: String,
        default: "#wnAppWarpper"
      }
    },
    data() {
      return {
        nowIndex: 0,
        containerDom: null,
        dewheel: null,
        positions: [],
        areapositions: []
      };
    },
    methods: {
      //忽略此方法
      setNowIndexByTarget(target) {
        let find;
        let index;
        for (let i = 0; i < this.list.length; i++) {
          let item = this.list[i];
          if (item.target === target) {
            find = item;
            index = i;
            break;
          }
        }
        this.onChange(find, index);
      },
      //点击的时候让其滚动到指定位置
      onChange(item, index) {
        this.getPositions();
        const go = () => {
          this.nowIndex = index;
          let toElement = document.querySelector(this.list[index].target);
          toElement.scrollIntoView({
            behavior: "smooth"
          });
        };
        let flag = true;
        //不考虑 clickable
        if (item.clickable) {
          flag = item.clickable(item, index);
        }
        if (flag) {
          go();
        }
      },
      //设定激活的类
      classable(item, index) {
        let clazz = index === this.nowIndex ? "navItem now" : "navItem";
        if (item.clazz) {
          clazz += " ";
          clazz += item.clazz(item, index);
        }
        if (item.clickable && !item.clickable(item, index)) {
          clazz += " old";
        }

        return clazz;
      },
      //获取各元素位置
      getPositions() {
        let last = 0;
        console.log("this.usedList",this.usedList)
        //[{target: "#Step1",text: "基础管理工作哦"},{target: "#Step2",text: "医保管理工作"}]
        this.positions = this.usedList.map((n, index) => {
          let dom = document.querySelector(n.target);
          let style = document.defaultView.getComputedStyle(dom);
          let marginTop = parseInt(style["marginTop"].replace("px", ""), 10);
          let marginBottom = parseInt(
            style["marginBottom"].replace("px", ""),
            10
          );
          //注意:是累加哦,offsetHeight就是高度 + padding + border
          last += dom.offsetHeight + marginTop + marginBottom;
          return last;
        });
        console.log("this.positions",this.positions)
        //[260,435] 获取位置
        this.areapositions = this.positions.map((n, index) => {
          let r = [];
          if (index === 0) {
            r[0] = 0;
            r[1] = n;
          } else {
            r[0] = this.positions[index - 1];
            r[1] = n;
          }
          return r;
        });
        console.log("this.areapositions",this.areapositions)
        // [[0, 260],[260, 435]] 获取位置
      },
      //滚动事件
      wheel(e) {
        //先获取位置
        this.getPositions();
        //元素被卷去的高
        let scrollTop = this.containerDom.scrollTop;
        //网页可见区域高
        let clientHeight = this.containerDom.clientHeight;
        //网页正文全文高
        let scrollHeight = this.containerDom.scrollHeight;
        //如果被卷去高度为0,说明当前第一个元素激活
        if (scrollTop === 0) {
          this.nowIndex = 0;
        }
        //如果被卷去的高度 = 总高度 - 可见区域的高,说明当前为最后一个元素
        else if (scrollTop === scrollHeight - clientHeight) {
          this.nowIndex = this.positions.length - 1;
        }
        //
        else {
          _.forEach(this.areapositions, (item, index) => {
            if (scrollTop >= item[0] && scrollTop <= item[1]) {
              this.nowIndex = index;
              return false;
            }
          });
        }
      }
    },
    computed: {
      //这个没有做clickable的处理,可忽略
      usedList() {
        return _.filter(this.list, (item, index) => {
          return !item.clickable || item.clickable(item, index);
        });
      }
    },
    mounted() {
      //使用loadsh自带的节流函数触发滚动事件函数
      this.dewheel = _.debounce(this.wheel, 300);
      this.containerDom = document.querySelector(
        this.container + " .el-scrollbar__wrap"
      );
      //绑定事件
      if (document.addEventListener) {
        this.containerDom.addEventListener("DOMMouseScroll", this.dewheel, false);
        this.containerDom.addEventListener("mousewheel", this.dewheel, false);
      } else {
        this.containerDom.onmousewheel = this.dewheel;
      }
    },
    destroyed() {
      //销毁事件
      if (document.addEventListener) {
        this.containerDom.removeEventListener(
          "DOMMouseScroll",
          this.dewheel,
          false
        );
        this.containerDom.removeEventListener("mousewheel", this.dewheel, false);
      } else {
        this.containerDom.onmousewheel = null;
      }
    }
  };
</script>

<style scoped lang="scss">
  .kpiRow {
    .el-col {
      border-left: 1px solid transparent;
      border-top: 1px solid transparent;
      border-bottom: 1px solid transparent;
      border-right: 1px solid #CECECE;
      height: 100%;
    }

    &:first-child {
      height: 40px;
      line-height: 40px;
      margin: 0 5px;
      text-align: center;

      .el-col {
        &:last-child {
          border-right: 1px solid transparent;
        }
      }
    }

    &:last-child {
      height: 40px;
      line-height: 40px;
      margin: 0 5px;
      border-bottom: 1px solid #CECECE;
      text-align: center;

      .el-col {
        &:last-child {
          border-right: 1px solid transparent;
        }
      }
    }
  }
  .anchorNavWarpper {
    width: 100%;
    height: 260px
  }

  .navItem {
    margin-bottom: 16px;
    min-width: 56px;
    height: 20px;
    font-size: 14px;
    font-weight: 500;
    line-height: 20px;
    color: #606266;
    position: relative;
    cursor: pointer;

    &:hover {
      color: #1b65b9;
    }

    &.now {
      color: #1b65b9;
    }

    &.old {
      cursor: default;
      color: #c0c4cc;

      &:hover {
        color: #c0c4cc;
      }
    }
  }

  .round {
    width: 8px;
    height: 8px;
    border-radius: 10px;
    border: 2px solid #1b65b9;
    position: absolute;
    left: -17px;
    top: 5px;
  }
</style>

上一篇:vue长列表虚拟滚动封装


下一篇:VUE Angular通用动态列表组件-亦可为自动轮播组件-01-根据数据量自动纵向滚动,鼠标划入停止滚动