第六天: 超詳情的vue錨點定位詳解

Js

20200422 vue自定義錨點定位

<template>
  <div>
    <!-- 內容區域 -->
    <div class="content">
      <div>
        content-0
      </div>
      <div>
        content-1
      </div>
      <div>
        content-2
      </div>
      <div>
        content-3
      </div>
      <div>
        content-4
      </div>
    </div>
    <!-- 導航區域 -->
    <ul class="navs">
      <li :class="{active: active===0}" @click="scrollTo(0)">
        content-0
      </li>
      <li :class="{active: active===1}" @click="scrollTo(1)">
        content-1
      </li>
      <li :class="{active: active===2}" @click="scrollTo(2)">
        content-2
      </li>
      <li :class="{active: active===3}" @click="scrollTo(3)">
        content-3
      </li>
      <li :class="{active: active===4}" @click="scrollTo(4)">
        content-4
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {},
  data() {
    return {
      active: 0 // 當前激活的導航索引
    }
  },
  mounted() {
    // 監聽滾動事件
    window.addEventListener('scroll', this.onScroll, false)
  },
  destroy() {
    // 必須移除監聽器,不然當該vue組件被銷燬了,監聽器還在就會出錯
    window.removeEventListener('scroll', this.onScroll)
  },
  methods: {
    // 滾動監聽器
    onScroll() {
      // 獲取所有錨點元素
      const navContents = document.querySelectorAll('.content div')
      // 所有錨點元素的 offsetTop
      const offsetTopArr = []
      navContents.forEach(item => {
        offsetTopArr.push(item.offsetTop)
      })
      // 獲取當前文檔流的 scrollTop
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      // 定義當前點亮的導航下標
      let navIndex = 0
      for (let n = 0; n < offsetTopArr.length; n++) {
        // 如果 scrollTop 大於等於第n個元素的 offsetTop 則說明 n-1 的內容已經完全不可見
        // 那麼此時導航索引就應該是n了
        if (scrollTop >= offsetTopArr[n]) {
          navIndex = n
        }
      }
      this.active = navIndex
    },
    // 跳轉到指定索引的元素
    scrollTo(index) {
      // 獲取目標的 offsetTop
      // css選擇器是從 1 開始計數,我們是從 0 開始,所以要 +1
      const targetOffsetTop = document.querySelector(`.content div:nth-child(${index + 1})`).offsetTop
      // 獲取當前 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 可以自己查一下,在這種場景下,相比 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)
        }
      }
    }
  }
  // 滾動監聽器
	onScroll() {
	  // 獲取所有錨點元素
	  const navContents = document.querySelectorAll('.content div')
	  // 所有錨點元素的 offsetTop
	  const offsetTopArr = []
	  navContents.forEach(item => {
	    offsetTopArr.push(item.offsetTop)
	  })
	  // 獲取當前文檔流的 scrollTop
	  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
	  // 定義當前點亮的導航下標
	  let navIndex = 0
	  for (let n = 0; n < offsetTopArr.length; n++) {
	    // 如果 scrollTop 大於等於第 n 個元素的 offsetTop 則說明 n-1 的內容已經完全不可見
	    // 那麼此時導航索引就應該是 n 了
	    if (scrollTop >= offsetTopArr[n]) {
	      navIndex = n
	    }
	  }
	  // 把下標賦值給 vue 的 data
	  this.active = navIndex
	}
},
mounted() {
 // 監聽滾動事件
  window.addEventListener('scroll', this.onScroll)
},
destroy() {
  // 必須移除監聽器,不然當該vue組件被銷燬了,監聽器還在就會出錯
  window.removeEventListener('scroll', this.onScroll)
},
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章