我们可以通过传递 behavior: smooth
平滑滚动到给定元素:
ele.scrollIntoView({ behavior: 'smooth' })
或将 CSS 属性滚动行为应用于目标元素:
scroll-behavior: smooth;
这两种方法我们之前介绍过,详细内容请查看滚动到页面顶部的多种实现。
IE 和 Safari 中不支持这两种方法,并且不允许自定义动画。
这篇文章介绍了一个平滑滚动的实现,它还允许我们自定义动画效果和持续时间。我们将在一个流行的用例中演示,用户可以通过单击相关的导航按钮在各个部分之间切换。
导航由一些 a
标签组成:
<a href="#section-1" class="trigger"></a>
<a href="#section-2" class="trigger"></a>
...
<div id="section-1">...</div>
<div id="section-2">...</div>
单击该链接将滚动页面至可由 href
属性确定的特定元素。
const triggers = [].slice.call(document.querySelectorAll('.trigger'))
triggers.forEach(function (ele) {
ele.addEventListener('click', clickHandler)
})
clickHandler
方法处理导航元素的单击事件。它根据 href
属性确定目标节点。请注意,我们将自己滚动到目标部分,因此默认操作将被忽略:
const clickHandler = function (e) {
// 阻止默认操作
e.preventDefault()
// 获取 href 属性
const href = e.target.getAttribute('href')
const id = href.substr(1)
const target = document.getElementById(id)
scrollToTarget(target)
}
如果您还没有看到 scrollToTarget
方法,请不要担心。顾名思义,该方法将滚动页面到给定的目标。
这是这篇文章的主要部分。要滚动到给定的点,我们可以使用 window.scrollTo(0,y)
,其中 y
表示从页面顶部到目标的距离。
也可以设置
behavior: 'smooth'
,但 IE 和 Safari 中不支持该选项。window.scrollTo({ top, left, behavior: 'smooth' })
我们要做的是在给定的持续时间内从起点移动到终点。
起点是当前 y
轴偏移 window.pageYOffset
终点是目标的顶部距离。它可以作为 target.getBoundingClientRect().top
进行检索
持续时间为毫秒数。您可以将其更改为可配置选项,但在本文中,它被设置为 800。
const duration = 800
const scrollToTarget = function (target) {
const top = target.getBoundingClientRect().top
const startPos = window.pageYOffset
const diff = top
let startTime = null
let requestId
const loop = function (currentTime) {
if (!startTime) {
startTime = currentTime
}
// 已用时间(毫秒)
const time = currentTime - startTime
const percent = Math.min(time / duration, 1)
window.scrollTo(0, startPos + diff * percent)
if (time < duration) {
// 继续前进
requestId = window.requestAnimationFrame(loop)
} else {
window.cancelAnimationFrame(requestId)
}
}
requestId = window.requestAnimationFrame(loop)
}
如您所见,我们告诉浏览器在下一次绘制之前执行循环函数。第一次,startTime
将被初始化为当前时间戳(currentTime
)。
然后我们计算已经过去了多少毫秒:
const time = currentTime - startTime
根据经过的时间和持续时间,很容易计算我们移动的百分比数,并滚动到该位置:
// percent 在 0 到 1 之间
const percent = Math.min(time / duration, 1)
window.scrollTo(0, startPos + diff * percent)
最后,如果还有剩余的时间,我们继续循环。否则,我们将取消最后一个请求:
if (time < duration) {
requestId = window.requestAnimationFrame(loop)
} else {
window.cancelAnimationFrame(requestId)
}
良好做法
当我们需要在给定的持续时间内在给定的点之间移动时,通常会想到使用
setTimeout()
或setInterval()
。但建议使用requestAnimationFrame
,它可以提供更好的动画性能。
目前,我们平均每毫秒移动到目标。我们每毫秒移动相同的距离。
如果需要,可以使用其他缓和功能替换当前的线性运动。看看这个网站,想象一下每种放松是如何产生不同的动画的。
下面的代码使用 easeInQuad
动画:
const easeInQuad = function(t) {
return t * t
}
const loop = function(currentTime) {
//...
const percent = Math.min(time / duration, 1)
window.scrollTo(0, startPos + diff * easeInQuad(percent))
})