vue3 directive做一個v-scroll-to吧!

看了一下vue-scroll-to套件用法後想說自己來寫一個簡單的!

用到的知識

  1. vue directive (自定義指令)
  2. window.scrollTo (移動瀏覽器位置)
  3. offsetTop ( 元素的頂端 )&& offsetHeight ( 元素的總高度 ) && winodow.scrollY ( 瀏覽器當前滾動了多少 )

代碼

    #使用
    <li v-scroll-to="{
                    target:'#tt', //目標容器
                    container:'#qq', //當前點擊目標的外容器有fix的header之類
                    behavior:'smooth' //要不要有平滑效果
             		}">1
     </li>
     <div class="h" id='tt'>2</div>
    #js
    const app = Vue.createApp({})
    app.directive('scroll-to', {
        mounted(el, binding, vnode) {
            let target = document.querySelector(binding.value.target) || null //目標dom
            let containerH = document.querySelector(binding.value.container).offsetHeight || 0 // 本身外容器高度
            let mode = binding.value.behavior || 'auto' //平滑功能
            el.addEventListener('click', function () {
                window.scrollTo({
                    top: target.offsetTop - containerH, //
                    behavior: 'smooth'
                })
            })
        }
    })
    app.mount('#app')

最後配上一個兼容插件完美解決safari沒有smooth的問題

<script src="https://cdn.jsdelivr.net/npm/smoothscroll-polyfill@0.4.4/dist/smoothscroll.min.js"></script>

進階版本(兼容自己寫,多了回調函式)

#知識點

  1. requestAnimationFrame 由瀏覽器來決定函數的執行時機
let scrollToAnimationFuncPools = []
function scrollToSmoothly (targetPos, time, mode, cb) {
  // 優化避免重複執行
  scrollToAnimationFuncPools.forEach(id => window.cancelAnimationFrame(id))
  scrollToAnimationFuncPools = []
  // 初始位置
  const currentPos = window.pageYOffset
  // 目標位置與初始位置一樣時直接返回
  if (currentPos === targetPos) return

  // 起始時間點初始化
  let startTime = null

  // 如果不需使用平滑功能直接使用scrollTo api
  if (mode !== 'smooth') {
    window.scrollTo({
      top: targetPos
    })
    return
  }

  // 平滑功能使用此函式
  const animationId = window.requestAnimationFrame(function step (currentTime) {
    // 記錄起始時間
    startTime = !startTime ? currentTime : startTime

    // 已經花費的時間
    const progress = currentTime - startTime

    // 當前時間點須要移動的距離
    const movePos = Math.abs(((targetPos - currentPos) * progress) / time)

    if (currentPos < targetPos) {
      // 目標位置低於當前位置滑動算式
      window.scrollTo(0, currentPos + movePos)
    } else {
      // 目標位置高於當前位置滑動算式
      window.scrollTo(0, currentPos - movePos)
    }

    //  經過的時間小於滑動要的總時間
    if (progress < time) {
      // 遞迴
      const animationId = window.requestAnimationFrame(step)
      scrollToAnimationFuncPools.push(animationId)
    } else {
      // 遞迴結束
      window.scrollTo(0, targetPos)
      if (cb) cb()
    }
  })
  scrollToAnimationFuncPools.push(animationId)
}

export default {
  install (Vue) {
    Vue.directive('scroll-to', {
      inserted (el, binding) {
        // 目標dom
        const target = document.querySelector(binding.value.target) || null
        // 本身外容器高度
        const containerH = document.querySelector(binding.value.container).offsetHeight || 0
        // 平滑功能
        const mode = binding.value.behavior || ''
        // 滑動總時間
        const time = binding.value.time || 500
        // 滑動時觸發回調
        const cb = binding.value.cb || null
        // 滑動到目標觸發回調
        const asyncCb = binding.value.syncCb || null
        el.addEventListener('click', function () {
          scrollToSmoothly(target.offsetTop - containerH, time, mode, asyncCb)
          if (cb) cb()
        })
      }
    })
  }
}
#使用
        <li
          v-scroll-to="{
            target:item.target, //目標容器
            container:'.huanan-header', //當前點擊目標的外容器有fix的header之類
            behavior:'smooth', //要不要有平滑效果
            cb:()=>console.log('sync'),//馬上觸發
            asyncCb:()=>console.log('async'),//滑動到目標才觸發
            time:1000 //要花多久時間
          }"
        >

參考

https://www.twblogs.net/a/5f0355a1d33e2533b0151aef