页面滑动太过生硬可以使用惯性滚动的模式,向下滚动一段距离慢慢停止,交互效果更好
export function useSmoothScroll() {
let rafId = null
let isScrolling = false
let currentScrollY = 0
let velocity = 0
let lastScrollY = 0
let lastTime = 0
let isWheeling = false
let wheelTimeout = null
const smoothScroll = () => {
if (!isScrolling) return
const currentTime = performance.now()
const rawDeltaTime = currentTime - lastTime
const deltaTime = Math.min(rawDeltaTime, 32) / 16.67 // 归一化到60fps,限制最大deltaTime
lastTime = currentTime
// 获取滚动边界
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
const minScroll = 0
// 检查是否到达边界
const atTop = currentScrollY <= minScroll
const atBottom = currentScrollY >= maxScroll
// 如果到达边界,立即停止并修正位置
if (atTop || atBottom) {
// 如果速度方向是朝向边界外的,立即停止
if ((atTop && velocity < 0) || (atBottom && velocity > 0)) {
velocity = 0
}
// 修正位置到边界
currentScrollY = atTop ? minScroll : maxScroll
window.scrollTo(0, currentScrollY)
// 如果速度很小,停止滚动
if (Math.abs(velocity) < 0.05) {
isScrolling = false
velocity = 0
return
}
}
// 如果速度很小,停止滚动(降低阈值,让滚动持续更久)
if (Math.abs(velocity) < 0.05) {
window.scrollTo(0, currentScrollY)
isScrolling = false
velocity = 0
return
}
// 只使用摩擦力让速度自然衰减(移除弹簧效果)
const friction = 0.95 // 摩擦力,控制惯性衰减速度(值越大衰减越慢,滚动距离更远)
// 更新速度(只衰减,不添加朝向目标的加速度)
velocity = velocity * friction
// 更新位置
currentScrollY += velocity * deltaTime
// 限制滚动范围(防止超出边界)
currentScrollY = Math.max(minScroll, Math.min(currentScrollY, maxScroll))
// 如果超出边界,立即停止速度
if (currentScrollY <= minScroll && velocity < 0) {
velocity = 0
currentScrollY = minScroll
}
if (currentScrollY >= maxScroll && velocity > 0) {
velocity = 0
currentScrollY = maxScroll
}
window.scrollTo(0, currentScrollY)
rafId = requestAnimationFrame(smoothScroll)
}
const handleWheel = (e) => {
e.preventDefault()
const delta = e.deltaY
const scrollSpeed = 1.2 // 增加滚动速度系数
// 获取当前滚动位置和边界
const currentPos = window.scrollY
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
const minScroll = 0
// 检查是否已经在边界
const atTop = currentPos <= minScroll
const atBottom = currentPos >= maxScroll
// 如果在边界且滚动方向是朝向边界外,不处理
if ((atTop && delta < 0) || (atBottom && delta > 0)) {
velocity = 0
return
}
// 直接给速度一个增量,而不是设置目标位置
// 这样滚动会自然衰减,不会像弹簧一样
const currentTime = performance.now()
const timeDelta = currentTime - lastTime || 16
// 计算速度增量(增加系数以获得更大的滚动距离)
const velocityIncrement = (delta * scrollSpeed) / timeDelta * 1.0
// 累加速度(可以累积多次滚动的速度)
velocity += velocityIncrement
// 增加最大速度限制,允许滚动更远
const maxVelocity = 30
velocity = Math.max(-maxVelocity, Math.min(velocity, maxVelocity))
lastTime = currentTime
// 标记正在滚动
isWheeling = true
// 清除之前的超时
if (wheelTimeout) {
clearTimeout(wheelTimeout)
}
// 如果停止滚动一段时间,开始惯性滚动
wheelTimeout = setTimeout(() => {
isWheeling = false
}, 50)
if (!isScrolling) {
isScrolling = true
currentScrollY = window.scrollY
lastScrollY = window.scrollY
rafId = requestAnimationFrame(smoothScroll)
}
}
const handleTouchStart = (e) => {
lastScrollY = window.scrollY
lastTime = performance.now()
velocity = 0
isWheeling = false
}
const handleTouchMove = (e) => {
const currentTime = performance.now()
const timeDelta = currentTime - lastTime
const currentPos = window.scrollY
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
const minScroll = 0
// 检查是否在边界
const atTop = currentPos <= minScroll
const atBottom = currentPos >= maxScroll
if (timeDelta > 0) {
const scrollDelta = currentPos - lastScrollY
// 如果在边界且滚动方向是朝向边界外,不更新速度
if ((atTop && scrollDelta < 0) || (atBottom && scrollDelta > 0)) {
velocity = 0
} else {
// 直接设置速度,不使用目标位置
velocity = scrollDelta / timeDelta * 0.5 // 触摸滚动惯性系数
}
}
lastScrollY = currentPos
lastTime = currentTime
currentScrollY = currentPos
if (!isScrolling) {
isScrolling = true
rafId = requestAnimationFrame(smoothScroll)
}
}
const handleTouchEnd = () => {
// 触摸结束后,确保位置在有效范围内
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
const currentPos = window.scrollY
// 如果超出边界,立即修正
if (currentPos < 0) {
window.scrollTo(0, 0)
currentScrollY = 0
velocity = 0
} else if (currentPos > maxScroll) {
window.scrollTo(0, maxScroll)
currentScrollY = maxScroll
velocity = 0
}
// 如果速度方向朝向边界外,停止速度
if ((currentPos <= 0 && velocity < 0) || (currentPos >= maxScroll && velocity > 0)) {
velocity = 0
}
}
onMounted(() => {
// 初始化
currentScrollY = window.scrollY
lastScrollY = window.scrollY
lastTime = performance.now()
// 使用被动监听器以提高性能(wheel需要非被动以preventDefault)
window.addEventListener('wheel', handleWheel, { passive: false })
window.addEventListener('touchstart', handleTouchStart, { passive: true })
window.addEventListener('touchmove', handleTouchMove, { passive: true })
window.addEventListener('touchend', handleTouchEnd, { passive: true })
})
onUnmounted(() => {
window.removeEventListener('wheel', handleWheel)
window.removeEventListener('touchstart', handleTouchStart)
window.removeEventListener('touchmove', handleTouchMove)
window.removeEventListener('touchend', handleTouchEnd)
if (rafId) {
cancelAnimationFrame(rafId)
}
if (wheelTimeout) {
clearTimeout(wheelTimeout)
}
})
}
使用,放到触发全局滚动条的位置,出现滚动条就可以应用, const friction = 0.95 // 摩擦力,控制惯性衰减速度(值越大衰减越慢,滚动距离更远)滚动的丝滑程度可以通过滑动系数来控制,值越大摩擦力越小
import { useSmoothScroll } from './composables/useSmoothScroll'
// 启用惯性滚动
useSmoothScroll()