在Web开发中,实现流畅的滚动效果对于提升用户体验至关重要。为了实现这一目标,开发人员可以利用一系列的滚动方案。其中,请求动画帧(requestAnimationFrame,简称rAF)是一种常用的技术。rAF通过优化动画效果的渲染,可以避免卡顿和过度绘制的问题。此外,还有其他滚动方案如CSS动画、滚动事件监听等等,开发人员可以根据具体需求选择合适的方案。通过合理选择和应用这些滚动方案,我们可以提供更加流畅和优化的用户体验。
Window 大小与文档大小
要获取窗口大小和文档大小,我们可以使用JavaScript编程语言。通过使用window对象的innerWidth和innerHeight属性,我们可以获取窗口的宽度和高度。而要获取文档的大小,我们可以使用document对象的clientWidth和clientHeight属性。这些属性将返回以像素为单位的值,从而使我们能够准确地确定窗口和文档的尺寸。通过使用这些属性,我们可以对网页进行响应式设计,并确保其在不同设备上的显示效果良好。
在 Chrome/Safari/Opera 中,如果没有滚动条,documentElement.scrollHeight
甚至可能小于 documentElement.clientHeight
为了可靠地获得完整的文档高度,我们应该采用以下这些属性的最大值:
let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
alert('Full document height, with scrolled out part: ' + scrollHeight);
为什么这样?最好不要问。这些不一致来源于远古时代,而不是“聪明”的逻辑。
获取当前滚动
获取文档或DOM元素当前滚动状态是前端开发中很常见的需求。根据标准,我们可以通过元素的scrollLeft
和scrollTop
属性来获取其当前水平和垂直滚动的像素位置。对于整个页面,我们可以使用document.documentElement
的这两个属性。但是,需要注意,在旧版的WebKit内核浏览器(如早期版本的Safari)中,这两个属性返回无效值,我们需要使用document.body
来取代。
为了兼容所有主流浏览器,一个更好的方式是直接使用window
对象的pageXOffset
和pageYOffset
属性。这两个属性分别返回页面内容区域从文档左上角滚动了多少像素,它们提供了一种跨浏览器兼容的方式来获取当前页面滚动状态。开发人员不必再记住各种浏览器的差异性,只需要调用这两个属性即可简单高效地实现功能。总体来说,获取滚动状态是前端开发中常见的需求之一。我们应该选择最简单高效且兼容所有主流浏览器的方式来实现它,那就是使用window.pageXOffset
和window.pageYOffset
属性。
alert('当前已从顶部滚动:' + window.pageYOffset);
alert('当前已从左侧滚动:' + window.pageXOffset);
这些属性是只读的。
Tips:
我们也可以从
window
的scrollX
和scrollY
属性中获取滚动信息由于历史原因,存在了这两种属性,但它们是一样的:
window.pageXOffset
是window.scrollX
的别名。window.pageYOffset
是window.scrollY
的别名。
基于浏览器API的滚动方法
scrollTo
scrollTo 方法用于将页面或元素滚动到指定位置。它接收两个参数,第一个参数是横坐标,第二个参数是纵坐标。
// 将页面滚动到坐标 (0, 100)
window.scrollTo(0, 100);
// 将元素 elem 滚动到坐标 (0, 0)
elem.scrollTo(0, 0);
scrollTo 方法支持传入behavior,这样可以实现平滑滚动效果。例如:
window.scrollTo({
top: 100,
behavior: 'smooth'
});
scrollTo 方法对整个页面和单个元素都起作用,常用于点击某个按钮后滚动到页面指定位置,或者滚动元素内部内容。
scrollBy
scrollBy 方法用于将页面或元素相对当前位置滚动指定的距离。
方法 scrollBy(x,y)
将页面滚动至 相对于当前位置的 (x, y)
位置。例如,scrollBy(0,10)
会将页面向下滚动 10px
。
// 让元素滚动
elem.scrollBy(300, 300);
使用 options
:
elem.scrollBy({
top: 100,
left: 100,
behavior: "smooth",
});
scrollIntoView
为了完整起见,让我们再介绍一种方法:elem.scrollIntoView(top)。
// 将元素 elem 滚动到可视区域
elem.scrollIntoView();
对 elem.scrollIntoView(top)
的调用将滚动页面以使 elem
可见。它有一个参数alignToTop
:
- 如果
top=true
(默认值),页面滚动,使elem
出现在窗口顶部。元素的上边缘将与窗口顶部对齐。 - 如果
top=false
,页面滚动,使elem
出现在窗口底部。元素的底部边缘将与窗口底部对齐。
亦或是接受一个包含以下属性的对象:
- behavior:定义滚动是立即的还是平滑的动画。可以取值为 smooth(平滑动画)、instant(立即发生)或 auto(由 scroll-behavior 的计算值决定)。
- block:定义垂直方向的对齐方式,可以取值为 start(顶部对齐)、center(居中对齐)、end(底部对齐)或 nearest(最近对齐)。默认为 start。
- inline:定义水平方向的对齐方式,可以取值为 start(左对齐)、center(居中对齐)、end(右对齐)或 nearest(最近对齐)。默认为 nearest。
const elem = document.getElementById("box");
elem.scrollIntoView();
elem.scrollIntoView(false);
elem.scrollIntoView({ block: "end" });
elem.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
实现滚动动画
使滚动动画并兼容非现代浏览器
behavior: "smooth"
等使用behavior参数的Scroll API需要在较高版本浏览器(实际上主要是Safari浏览器版本要求较高):
<img src="https://fs.lwmc.net/uploads/2023/10/1696517179824-202310052246610.webp" alt="image-20231005224618442" style="zoom: 67%;" />
如图所示的window.scrollTo
API 中behavior参数的兼容性,所以通常需要补充一下非现代浏览器的方法作为兜底:
实现基于raf的滚动函数
ScrollTo
/** * @description 基于raf的滚动函数 * @param el 元素 * @param to 目标滚动位置 * @param duration 动画时长 ms */ export const rAFScrollTo = (el: HTMLElement, to: number, duration: number) => { const start = el.scrollTop const change = to - start const increment = 1000 / 60 let currentTime = 0 let requestId: number | undefined const cancelRAF = () => { if (requestId) { cancelAnimationFrame(requestId) window.removeEventListener('wheel', cancelRAF) } } // 监听鼠标滚轮 window.addEventListener('wheel', cancelRAF) const animateScroll = () => { currentTime += increment el.scrollTop = easeInOutQuad(currentTime, start, change, duration) if (currentTime < duration) { requestId = requestAnimationFrame(animateScroll) } } const easeInOutQuad = (t: number, b: number, c: number, d: number) => { t /= d / 2 if (t < 1) return (c / 2) * t * t + b t-- return (-c / 2) * (t * (t - 2) - 1) + b } animateScroll() }
scrollBy 兼容在非现代浏览器的平滑滚动
/** * @description scrollBy 兼容在非现代浏览器的平滑滚动 * @param options 传入参数 * @param options.el 元素 * @param options.offset 滚动相对距离 * @param options.duration 动画时长 ms * @param options.useRAF 使用RAF动画 */ export const scrollBy = ({ el, offset, duration = 500, useRAF = false, }: { el: HTMLElement offset: number duration: number useRAF?: boolean }) => { if ( typeof window.getComputedStyle(document.body).scrollBehavior == 'undefined' || useRAF ) { // 传统的JS平滑滚动处理代码 const start = el.scrollTop rAFScrollTo(el, start + offset, duration) } else { el.scrollBy({ top: offset, behavior: 'smooth', }) } }
scrollIntoView 兼容在非现代浏览器的平滑滚动
/** * @description scrollIntoView 兼容在非现代浏览器的平滑滚动 * @param options 传入参数 * @param options.el 元素 * @param options.scrollMarginTop 滚动时距离viewport的上边距 * @param options.duration 动画时长 ms * @param options.useRAF 使用RAF动画 */ export const scrollIntoView = ({ el, scrollMarginTop = 0, duration = 500, useRAF = false, }: { el: HTMLElement scrollMarginTop: number duration: number useRAF?: boolean }) => { if ( typeof window.getComputedStyle(document.body).scrollBehavior == 'undefined' || useRAF ) { const { top } = el.getBoundingClientRect() const pageYOffset = window.pageYOffset const to = top + pageYOffset - scrollMarginTop rAFScrollTo(document.documentElement, to, duration) } else { el.style.scrollMarginTop = scrollMarginTop + 'px' el.scrollIntoView({ behavior: 'smooth', }) el.style.scrollMarginTop = '0px' } }
禁止滚动
有时候我们需要使文档“不可滚动”。要使文档不可滚动,只需要设置 document.body.style.overflow = "hidden"
。该页面将“冻结”在其当前滚动位置上。这个方法的缺点是会使滚动条消失。如果滚动条占用了一些空间,它原本占用的空间就会空出来,那么内容就会“跳”进去以填充它。
这看起来有点奇怪,但是我们可以对比冻结前后的 clientWidth
。如果它增加了(滚动条消失后),那么我们可以在 document.body
中滚动条原来的位置处通过添加 padding
,来替代滚动条,这样这个问题就解决了。保持了滚动条冻结前后文档内容宽度相同。
亦或是参考这篇文章:css - 如何解决滚动条scrollbar出现造成的页面宽度被挤压的问题? - 个人文章 - SegmentFault 思否
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。