頁面滾動自動錨點定位 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 的滾動事件,經過滾動高度來判斷那個內容區在當前視口, 從而操做對應的導航菜單裏的狀態的轉換。 點擊導航菜單觸發滾動, 與此相對應。

固然我解決平滑滾動過程當中重複監聽的辦法其實並不完美,若有更好的思路和建議,歡迎留言~~~

感激~~~

相關文章
相關標籤/搜索