下面我将从基础到高级,全面介绍如何监听滚动条,并提供代码示例和最佳实践。

基础方法:scroll 事件
这是最直接、最基础的方法,当用户滚动一个元素时,该元素会触发 scroll 事件。
如何使用?
你可以通过以下几种方式来监听:
a. HTML 内联事件 (不推荐)
<div id="myContainer" style="height: 200px; overflow-y: scroll; border: 1px solid #ccc;">
<p>滚动我试试...</p>
<!-- 很多内容 -->
</div>
<script>
function handleScroll() {
console.log('滚动事件被触发了!');
}
</script>
b. JavaScript DOM 事件监听器 (推荐)

这是最常用和推荐的方式。
// 获取要监听的元素
const myContainer = document.getElementById('myContainer');
// 定义一个处理函数
function handleScroll(event) {
// event.currentTarget 指向绑定事件的元素
const element = event.currentTarget;
// 获取滚动条的垂直位置
const scrollTop = element.scrollTop;
// 获取滚动条的垂直最大可滚动距离
const scrollHeight = element.scrollHeight;
// 获取元素自身的高度
const clientHeight = element.clientHeight;
// 计算滚动百分比
const scrollPercentage = (scrollTop / (scrollHeight - clientHeight)) * 100;
console.log(`当前滚动位置: ${scrollTop}`);
console.log(`滚动百分比: ${scrollPercentage.toFixed(2)}%`);
// 示例:滚动到底部时执行操作
if (scrollTop + clientHeight >= scrollHeight - 5) { // 减去一个5px的容差
console.log('已经滚动到底部了!');
// 在这里执行你的逻辑,比如加载更多数据
}
}
// 添加事件监听器
myContainer.addEventListener('scroll', handleScroll);
关键属性
在 scroll 事件处理函数中,你通常会用到以下属性:
event.currentTarget(或直接使用element): 触发事件的 DOM 元素。element.scrollTop: 获取或设置元素垂直方向滚动条的位置。element.scrollLeft: 获取或设置元素水平方向滚动条的位置。element.scrollHeight: 元素内容的总高度(包括由于溢出而不可见的部分)。element.clientHeight: 元素的内部高度(包括内边距,但不包括滚动条、边框和外边距)。
监听整个页面的滚动
如果你想监听的是整个浏览器窗口的滚动(固定导航栏、返回顶部按钮等),你需要监听 window 对象的 scroll 事件。
// 定义处理函数
function handlePageScroll() {
// 使用 window.pageYOffset 或 document.documentElement.scrollTop 获取页面滚动位置
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
console.log(`页面已滚动: ${scrollTop}px`);
// 示例:当页面滚动超过100px时,显示一个返回顶部按钮
const backToTopButton = document.getElementById('backToTopBtn');
if (scrollTop > 100) {
backToTopButton.style.display = 'block';
} else {
backToTopButton.style.display = 'none';
}
}
// 在 window 对象上添加事件监听器
window.addEventListener('scroll', handlePageScroll);
注意:window.scrollY 是现代浏览器中获取垂直滚动位置的推荐标准属性,比 window.pageYOffset 更直观。

性能优化:节流与防抖
scroll 事件有一个非常显著的特点:触发频率极高,用户滚动时,它可能会在短时间内触发几十甚至上百次,如果事件处理函数中包含复杂的计算或 DOM 操作,这会导致严重的性能问题,页面变得卡顿。
为了解决这个问题,我们需要使用 节流 和 防抖 技术。
节流
作用:确保函数在指定的时间间隔内最多执行一次。 场景:适合用于需要平滑、连续反馈的场景,比如实时显示滚动百分比。
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 使用节流包装后的函数
window.addEventListener('scroll', throttle(handlePageScroll, 100)); // 每100ms最多执行一次
防抖
作用:确保函数在停止滚动一段时间后才执行,如果在这段时间内又触发了滚动,则重新计时。 场景:适合用于在用户停止滚动后才执行的操作,比如判断是否滚动到底部并加载更多数据。
function debounce(func, delay) {
let timeoutId;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// 使用防抖包装后的函数
window.addEventListener('scroll', debounce(handlePageScroll, 200)); // 停止滚动200ms后执行
最佳实践:在滚动事件处理中,强烈建议使用节流或防抖来优化性能。
现代方法:Intersection Observer API
对于很多滚动检测场景(如“元素是否进入视口”),传统的 scroll + getBoundingClientRect() 方法虽然可行,但性能不佳,因为它需要频繁计算和触发主线程的布局重排。
Intersection Observer API (交叉观察器) 是一个现代的、高性能的 API,专门用于异步观察一个元素与另一个元素(或视口)的交叉状态。
核心优势
- 高性能:由浏览器在底层进行优化,不会在每次滚动时都触发 JavaScript,只在交叉状态发生改变时通知你。
- 简单易用:API 设计简洁,避免了手动计算和事件监听。
如何使用?
<div style="height: 2000px; background: #f0f0f0;"> <h1>滚动页面...</h1> </div> <!-- 这个是我们想要观察的元素 --> <div id="targetBox" style="width: 200px; height: 200px; background: dodgerblue; margin: 500px auto; color: white; text-align: center; line-height: 200px;"> 我是被观察的盒子 </div> <div style="height: 1000px; background: #f9f9f9;"> </div>
// 1. 获取目标元素
const targetBox = document.getElementById('targetBox');
// 2. 定义当交叉状态改变时执行的回调函数
const callback = (entries, observer) => {
entries.forEach(entry => {
// entry.isIntersecting 是一个布尔值,表示目标元素是否与根元素相交
if (entry.isIntersecting) {
console.log('目标元素已经进入视口!');
targetBox.style.background = 'green';
// 如果只需要触发一次,可以在这里取消观察
// observer.unobserve(targetBox);
} else {
console.log('目标元素已经离开视口。');
targetBox.style.background = 'dodgerblue';
}
});
};
// 3. 创建一个 IntersectionObserver 实例
// root: 指定哪个元素作为视口,默认是浏览器视口
// threshold: 一个阈值数组,表示交叉比例达到多少时触发回调,可以是 0, 0.5, 1 等
const observer = new IntersectionObserver(callback, {
root: null, // 使用浏览器视口作为根
threshold: 0.5 // 当目标元素有50%进入视口时触发
});
// 4. 开始观察目标元素
observer.observe(targetBox);
何时使用 Intersection Observer?
- 懒加载图片或 iframe。
- 无限滚动加载更多内容。
- 检测广告是否在视口中,以便进行计费。
- 实现滚动时的动画效果。
- 几乎所有需要“元素是否在视口中”的场景。
总结与选择
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
scroll 事件 |
需要获取实时的滚动位置(如滚动百分比、固定导航栏)。 | 直观,易于理解。 | 性能差,必须配合节流/防抖使用。 |
| Intersection Observer | 检测元素是否进入/离开视口(如懒加载、无限滚动、动画触发)。 | 性能极佳,API 简洁,代码更清晰。 | 不适合需要实时获取滚动位置的场景。 |
resize 事件 |
监听窗口大小变化(常与滚动结合使用,例如响应式布局)。 | - | 同样需要节流/防抖优化。 |
简单决策指南:
- 我需要知道用户滚动到了页面的哪个位置吗?
- 是 -> 使用
window.addEventListener('scroll', throttle(yourFunction))。
- 是 -> 使用
- 我需要知道某个特定的元素是否出现在了屏幕上吗?
- 是 -> 首选
Intersection Observer API,它是为此而生的最佳工具。
- 是 -> 首选
- 我需要监听一个特定容器(非窗口)的内部滚动吗?
- 是 -> 使用
element.addEventListener('scroll', yourFunction)。
- 是 -> 使用
对于现代 Web 开发,Intersection Observer 应该成为你处理“元素与视口关系”时的首选方案,而 scroll 事件则保留在真正需要“滚动位置”的场景中,并务必记得进行性能优化。
