【vue组件封装】根据页面滚动高亮显示目录的侧边栏

请添加图片描述


前言

现在可以说整个的大前端开发都是组件化的天下,无论从三大框架(Vue、React、Angular),还是跨平台方案的Flutter,甚至是移动端都在转向组件化开发。

所以,学习组件化最重要的是它的思想,每个框架或者平台可能实现方法不同,但是思想都是一样的。
为了让新手也能看懂代码我做了详细的代码注释,希望这篇文章能帮助到您!


一、了解HTML DOM 属性(clientHeight、offsetHeight、scrollHeight、offsetTop、scrollTop)

在我们封装侧边栏组件前需要先了解前置知识 HTML DOM 属性,那我们先简单了解一下这几个DOM属性吧!!
请添加图片描述
网页可见区域高:document.body.clientHeight
网页正文全文高:document.body.scrollHeight
网页可见区域高(包括边线的高):document.body.offsetHeight
网页被卷去的高:document.body.scrollTop
屏幕分辨率高:window.screen.height

二、开始封装appsideNavBar侧边栏组件

1.组件需求分析和核心思路解析

1.1、首先组件需要在滚动中高亮显示当前内容的标题

为了让侧边栏在滚动中高亮显示标题,我们可以封装两个方法来实现
监听页面的滚动 和 获取页面中每一栏内容的高度
有了这两个方法,我们就知道了每一栏内容的高度,并且滚动到下一个内容的高度时通过js改变标题的亮度
请添加图片描述
代码示例:

// 获取页面中每一栏内容的高度数组
    getChildrenHeigh() {
      // 获得他们的父元素节点
      let pageScrooll = document.querySelector(".side-navBar").parentNode;
      let arr = [];
      // console.log(this.navLists.length);
      // 将所有子元素的高度放入arr
      for (let i = 0; i < this.navLists.length; i++) {
        // 把所有获得到的子元素高度都放入arr中
        arr.push(pageScrooll.children[i].offsetTop);
      }
      // 给这个arr兜底,这样可以解决最后一个内容栏监听不到的bug,注意:如果最后一栏内容高度低于滚动条当前的高度,也不会高亮显示最后一栏的标题哦
      arr.push(Number.MAX_VALUE);
      this.ContentHeightList = arr;
      // console.log(this.ContentHeightList);
    },
    // 监听滚轮
    handleScroll() {
      // 获得当前的滚轮高度
      var scrollTop =
        window.pageYOffset ||
        document.documentElement.scrollTop ||
        document.body.scrollTop;
      let Heights = this.ContentHeightList;
      // 只有ContentHeightList存在才监听当前高度
      if (this.ContentHeightList) {
        // console.log(scrollTop);
        for (let i = 0; i < Heights.length; i++) {
          // 如果滚轮高度大于当前所在的子元素高度 并且 滚轮高度小于下一个子元素的高度 ,那么说明滚轮在当前内容中,就激活当前的nav栏
          if (scrollTop >= Heights[i] && scrollTop <= Heights[i + 1]) {
            // console.log(i);
            // 那么就激活当前的nav栏
            this.moveIndex = i;
            // return false;
          }
        }
      }
    }

1.2、通过点击侧边栏的标题自动滚动到相应的内容

这个需求比较简单,我们可以在标题的标签上绑定点击事件,在用户点击后滚动到相应内容上

这里有两种方法可以实现需求分别是 “a标签锚点” 和 “window.scrollTo”,这里我用的方法是后者,因为a标签锚点虽然可以实现点击后滚动到相应内容,但也会在地址栏中显示锚点名字非常的不美观,并且没有滚动动画
请添加图片描述

// 点击nav栏后
    itemClick(index) {
      // 获取父元素的dom元素
      let pageScrooll = document.querySelector(".side-navBar").parentNode;
      // console.log(pageScrooll.children[index].offsetTop + 520);
      // 激活当前高亮nav栏
      this.moveIndex = index;
      // 点击后滚动到相应的区域
      window.scrollTo({
        top: pageScrooll.children[index].offsetTop + 520,
        behavior: "smooth"
      });
    },

2.总体代码演示

!!!注意本组件只允许放在需要监听滚动页面的最底部,因为我们是通过父元素的节点获得内部所有子元素的高度所以必须要放在父元素中的最底部

子组件“appsideNavBar”代码如下(示例):

<template>
  <ul class="side-navBar">
    <li
      @click="itemClick(index)"
      v-for="(item, index) in navLists"
      :key="index"
      :class="moveIndex === index ? 'activeLight' : ''"
    >
      {{ item }}
    </li>
  </ul>
</template>

<script>
// !!!注意本组件只允许放在需要监听滚动页面的最底部,因为我们是通过父元素的节点获得内部所有子元素的高度所以必须要放在父元素中的最底部
export default {
  props: {
    navLists: Array //每个标题内容
  },
  data() {
    return {
      moveIndex: 0, //当前激活nav栏高亮显示标题的下标
      ContentHeightList: null //页面中每一栏内容的高度数组
    };
  },
  methods: {
    // 点击nav栏后
    itemClick(index) {
      // 获取父元素的dom元素
      let pageScrooll = document.querySelector(".side-navBar").parentNode;
      // console.log(pageScrooll.children[index].offsetTop + 520);
      // 激活当前高亮nav栏
      this.moveIndex = index;
      // 点击后滚动到相应的区域
      window.scrollTo({
        top: pageScrooll.children[index].offsetTop, //举例:用户点击 第二个标签后 页面就会滚动到第二个标签的高度
        behavior: "smooth" //丝滑滚动
      });
    },
    // 获取页面中每一栏内容的高度数组
    getChildrenHeigh() {
      // 获得他们的父元素节点
      let pageScrooll = document.querySelector(".side-navBar").parentNode;
      let arr = [];
      // console.log(this.navLists.length);
      // 将所有子元素的高度放入arr
      for (let i = 0; i < this.navLists.length; i++) {
        // 把所有获得到的子元素高度都放入arr中
        arr.push(pageScrooll.children[i].offsetTop);
      }
      // 给这个arr兜底,这样可以解决最后一个内容栏监听不到的bug,注意:如果最后一栏内容高度低于滚动条当前的高度,也不会高亮显示最后一栏的标题哦
      arr.push(Number.MAX_VALUE);
      this.ContentHeightList = arr;
      // console.log(this.ContentHeightList);
    },
    // 监听滚轮
    handleScroll() {
      // 获得当前的滚轮高度
      var scrollTop =
        window.pageYOffset ||
        document.documentElement.scrollTop ||
        document.body.scrollTop;
      let Heights = this.ContentHeightList;
      // 只有ContentHeightList存在才监听当前高度
      if (this.ContentHeightList) {
        // console.log(scrollTop);
        for (let i = 0; i < Heights.length; i++) {
          // 如果滚轮高度大于当前所在的子元素高度 并且 滚轮高度小于下一个子元素的高度 ,那么说明滚轮在当前内容中,就激活当前的nav栏
          if (scrollTop >= Heights[i] && scrollTop <= Heights[i + 1]) {
            // console.log(i);
            // 那么就激活当前的nav栏
            this.moveIndex = i;
            // return false;
          }
        }
      }
    }
  },
  mounted() {
    // 监听滚动
    window.addEventListener("scroll", this.handleScroll, true);
    // 获得内容高度
    this.getChildrenHeigh();
  },
  destroyed() {
    this.ContentHeightList = null;
    window.removeEventListener("scroll", this.handleScroll, true);
  }
};
</script>

<style lang="less" scoped>
.side-navBar {
  transition: 0.5s linear all;
  position: fixed;
  top: 280px;
  left: 0px; //改为right 就是右侧边栏
  width: 130px;
  background: #ffffff;
  box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
  border-radius: 0px 10px 10px 0px;
  z-index: 99999;
  padding: 43px 0 0 0;
  li {
    margin-bottom: 40px;
    font-size: 14px;
    font-family: PingFangSC-Regular, PingFang SC;
    font-weight: 400;
    color: rgba(0, 0, 0, 0.85);
    line-height: 20px;
    cursor: pointer;
    padding-left: 43px;
    position: relative;
  }
}
.activeLight {
  color: #096dd9 !important;
}

</style>

父组件代码如下(示例):

<template>
  <div class="demo">
    <div class="container" style="  background-color: #eee;" v-for="item in navLists" :key="item">{{item}}</div>
    
    <appsideNavBar :navLists="navLists"></appsideNavBar>

    <div style="height:500px"></div>
  </div>
</template>

<script>
import appsideNavBar from './appsideNavBar.vue'
export default {
  components:{
    appsideNavBar
  },
  data() {
    return {
      //nav栏的文字
      navLists: ["标题1", "标题2", "标题3", "标题4","标题5"],
    }
  }
}
</script>

<style scoped>
.container {
  width: 100%;
  height: 600px;
  font-size: 80px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 2px solid black;
}
</style>

3.功能需求拓展

这是一个十分灵活的组件,为了有更好的体验和满足业务需求,我们还可以在原基础上简单拓展:
因为监听的是父元素滚动时的内容,所以当我们不需要监听别的元素时,可以把需要监听的内容放在一个div中 把不需要监听的内容放在另一个div中,两个div是兄弟关系,这样就可以做到完美的监听咯~

我们只要修改父组件代码(示例):

<template>
  <div class="demo">
    <!-- 其他区域 -->
    <div style="height:500px">
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt nostrum
      amet reiciendis blanditiis culpa aperiam, ipsam illo cumque qui beatae
      pariatur illum provident tenetur fugiat velit dolorem expedita harum modi.
    </div>
      <!-- 需要监听侧边栏的区域 -->
    <div class="others">
      <div
        class="container"
        style="  background-color: #eee;"
        v-for="item in navLists"
        :key="item"
      >
        {{ item }}
      </div>
      <appsideNavBar :navLists="navLists"></appsideNavBar>
    </div>
    
    <!-- 其他区域 -->
    <div style="height:500px"></div>
  </div>
</template>

<script>
import appsideNavBar from "./appsideNavBar.vue";
export default {
  components: {
    appsideNavBar
  },
  data() {
    return {
      //nav栏的文字
      navLists: ["标题1", "标题2", "标题3", "标题4", "标题5"]
    };
  }
};
</script>

<style scoped>
.container {
  width: 100%;
  height: 600px;
  font-size: 80px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 2px solid black;
}
</style>

因为只是简单的演示,在拓展中的内容还是会出现小小的逻辑错误,我们可以在监听滚动条时对侧边栏做出相应的显示隐藏或者吸顶来防止这样的错误,这里字数已经很多了,就先不做拓展了


总结

以上就是今天要讲的内容,本文仅仅简单介绍了侧边栏组件的封装,而它还可以拓展更多的功能和交互,例如“侧边栏隐藏/显示”、“侧边栏样式更改”、"侧边栏吸顶”。
把css样式改动一下,就可以做成顶部栏!
码字不易,有不懂的内容可以联系我哦~

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值