页面滚动自动锚点定位 Tab 处理

这是我参与更文挑战的第 12 天,活动详情查看: 更文挑战css

Lynne,一个能哭爱笑永远少女心的前端开发工程师。身处互联网浪潮之中,热爱生活与技术。前端

前言

首先描述下问题,应用场景以下:web

随着滚动条的滚动,tab会对应进行切换,切换tab时,又会定位到tab对应内容的高度变化。数组

切换 tab 定位到对应内容这个能够用简单的锚点定位来实现,但若是须要平滑地进行内容滚动切换最好借助scrollIntoView 来实现,而要实现tab随滚动条的滚动进行切换则须要监听当前页面的滚动。markdown

接下来以一段代码为例,分析实现思路。async

<template>
  <div class="box">
    <div class="tab" ref="tab">
      <div v-for="(item, index) in tabs" :key="index">
        <div :class="{ active: active === index }" @click="switchTab(index)">
          {{ item }}
        </div>
      </div>
    </div>
    <div class="cont" ref="cont">
      <div class="cont_1" ref="cont_1">内容一</div>
      <div class="cont_2" ref="cont_2">内容二</div>
      <div class="cont_3" ref="cont_3">内容三</div>
    </div>
    <div class="back-top" @click="backTop"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tabs: ["tab1", "tab2", "tab3"],
      active: 0,
      cont1: null,
      cont2: null,
      cont3: null,
      isClickTab: false
    };
  },
  methods: {
    backTop() { // 回到顶部
      this.cont1.scrollIntoView({
        block: "start",
        behavior: "smooth"
      });
    },
    switchTab(index) { // 根据当前index切换到对应的页面内容
      if (index === 0) {
        this.cont1.scrollIntoView({
          block: "start",
          behavior: "smooth"
        });
      } else if (index === 1) {
        this.cont2.scrollIntoView({
          block: "start",
          behavior: "smooth"
        });
      } else {
        this.cont3.scrollIntoView({
          block: "start",
          behavior: "smooth"
        });
      }
    }
  },
  mounted() {
    this.cont1 = this.$refs["cont_1"];
    this.cont2 = this.$refs["cont_2"];
    this.cont3 = this.$refs["cont_3"];
    const tabH = this.$refs["tab"].offsetHeight;
    // 添加scroll事件监听
    this.$refs["cont"].addEventListener("scroll", () => {
      if (this.cont3.getBoundingClientRect().top <= tabH) {
        this.active = 2;
        return false;
      }
      if (this.cont2.getBoundingClientRect().top <= tabH) {
        this.active = 1;
        return false;
      }
      if (this.cont1.getBoundingClientRect().top <= tabH) {
        this.active = 0;
      }
    });
  }
};
</script>
<style lang="scss" scoped>
.box {
  font-size: 28px;
  overflow-x: auto;
  height: 100vh;
  display: -webkit-flex;
  display: flex;
  flex-direction: column;
  overflow-y: hidden;
  .tab {
    height: 88px;
    background: #fff;
    line-height: 88px;
    color: #666;
    display: -webkit-flex;
    display: flex;
    justify-content: space-around;
    .active {
      font-size: 32px;
      color: #333;
      &::after {
        display: block;
        content: "";
        width: 36px;
        height: 6px;
        margin: auto;
        margin-top: -10px;
        background: rgba(255, 51, 0, 1);
        border-radius: 3px;
      }
    }
  }
  .cont {
    height: 300px;
    flex-grow: 1;
    overflow: auto;
    .cont_1 {
      height: 400px;
      background: pink;
    }
    .cont_2 {
      height: 800px;
      background: yellow;
    }
    .cont_3 {
      height: 100%;
      background: lightgreen;
    }
  }
  .back-top {
    width: 80px;
    height: 80px;
    background: url(../../assets/back-top.png) center / 100%
      100% no-repeat;
    border-radius: 50%;
    position: fixed;
    bottom: 120px;
    right: 32px;
  }
}
</style>
复制代码

这段代码是参考网上的,相对来讲简单一些,由于tab项的数量固定,咱们只要根据tab点击时index切换和监听高度切换至对应index的tab便可。ide

但若是数组 tab 的元素数量未知,咱们应该如何处理?这种数量不肯定的场景更为通用,接下来抽象出通用代码实现流程。post

举个栗子:flex

<div class="box">
 <div class="tab" ref="tab">
  <div v-for="(item, index) in tabs" :key="index">
    <div :class="{ active: active === index }" @click="switchTab(index)">
      {{ item }}
    </div>
  </div>
 </div>
 <div class="tab-content-list">
   <tab-content :id="`to-${index}`" v-for="(data, index) in dataList" :key="index" :resData="data" ref="content" />
 </div>
</div>
复制代码

tab 切换时平滑滚动定位

tab-content 中接收父组件内容进行展现:this

props: {
  resData: Object
}
复制代码

使用scrollIntoView方法实现平滑滚动:

methods: {
  switchTab(index) {
      this.clickedIndex = index
      // 定位到当前tab对应的内容
      document.getElementById(`to-${clickedIndex}`).scrollIntoView({
        block: 'start',
        behavior: 'smooth'
      })
    }
}
复制代码

监听滚动实现tab切换

在mounted中收集滚动元素的高度并初始化挂载滚动监听:

mounted() {
  // 初始化滚动监听
  this.scrollInit()
  this.isSlide = false // 平滑滚动前
  // 收集收集滚动元素的高度
  this.slotsTopList = this.$refs.content.map((item, index) => {
    return item.offsetTop
  })

  setTimeout(() => { // 点击时平滑滚动完成前不监听滚动
    this.isSlide = false
  }, 600)
}
复制代码

具体方法实现:

methods: {
    // 监听滚动高度
    scrollInit () {
      this.throttleGetScrollTop = _.throttle(this.getScrollTop, 100)
      this.throttleGetScrollTop()
      window.addEventListener('scroll', this.throttleGetScrollTop, true)
    },
    // 监听滚动距离
    async getScrollTop () {
      const scrollTop = document.getElementById('privilage-list').scrollTop
      // 获取当前滚动内容对应的tabIndex
      const currentIndex = await this.slotsTopList.findIndex((top, index) => {
        return scrollTop >= this.slotsTopList[index + 1] && scrollTop < this.slotsTopList[index + 2]
      })
      if (this.isSlide) return
      this.clickedIndex = currentIndex + 1
    }
}
复制代码

有一点小问题是,在点击时的滚动中也会发生滚动监听致使tab有一次来回切换,但目前又没办法确切知道点击平滑滚动完成的事件,所以我在点击切换tab时间中加了一个标记和延迟,以免重复监听:

this.isSlide = false // 平滑滚动前

setTimeout(() => { // 点击时平滑滚动完成前不监听滚动
  this.isSlide = false
}, 600)
复制代码

而后在滚动监听高度时间中加入if (this.isSlide) return

总结

以上是页面滚动锚点定位tab的实现原理和实现流程,最主要的是经过监听 window 的滚动事件,经过滚动高度来判断那个内容区在当前视口, 从而操做对应的导航菜单里的状态的转换。 点击导航菜单触发滚动, 与此相对应。

固然我解决平滑滚动过程当中重复监听的办法其实并不完美,若有更好的思路和建议,欢迎留言~~~

感激~~~

相关文章
相关标签/搜索