看了一下vue-scroll-to套件用法後想說自己來寫一個簡單的!
用到的知識
- vue directive (自定義指令)
- window.scrollTo (移動瀏覽器位置)
- 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>
進階版本(兼容自己寫,多了回調函式)
#知識點
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 //要花多久時間
}"
>
參考