应使用节流或防抖优化 scroll/resize 事件,推荐 requestAnimationFrame 节流滚动、ResizeObserver 监听尺寸变化、IntersectionObserver 处理视口进入逻辑,并注意移除监听器与避免强制同步布局。

页面滚动和视口大小变化是前端开发中高频触发的交互场景,用好 scroll 和 resize 事件能提升性能与体验,但直接监听容易引发性能问题。关键在于节流(throttle)或防抖(debounce),并合理使用现代 API 替代方案。
scroll 事件:监听滚动位置变化
滚动事件频繁触发(每像素都可能触发),不加控制会导致卡顿。推荐使用 requestAnimationFrame 节流或 lodash.throttle 等工具。
- 基础监听写法(不推荐直接使用):
window.addEventListener('scroll', () => { console.log(window.scrollY); }); - 推荐节流方案(原生实现):
声明一个标志位,确保每次滚动帧只执行一次逻辑:
let ticking = false;<br> window.addEventListener('scroll', () => {<br> if (!ticking) {<br> requestAnimationFrame(() => {<br> console.log(window.scrollY);<br> ticking = false;<br> });<br> ticking = true;<br> }<br> }); - 若需精确监听某个元素滚动(如容器内滚动),绑定到该元素而非
window,例如:document.querySelector('.list-container').addEventListener('scroll', handler);
resize 事件:监听视口尺寸变化
视口缩放、浏览器窗口拖拽、横竖屏切换都会触发 resize。它同样高频,且在移动端还可能因软键盘弹出/收起而意外触发。
- 避免直接监听:
window.addEventListener('resize', () => { updateLayout(); });—— 易造成重绘风暴 - 推荐使用
ResizeObserver(现代标准,更精准、低开销):
const ro = new ResizeObserver(entries => {<br> for (let entry of entries) {<br> console.log(entry.contentRect.width, entry.contentRect.height);<br> }<br> });<br> ro.observe(document.body); // 或指定任意 DOM 元素 - 兼容性兜底(IE11 及旧版 Safari)仍可用节流
resize,但仅对window生效;ResizeObserver不支持监听 window,需结合window.innerWidth+ 节流手动判断尺寸变化
替代方案:Intersection Observer 更适合“滚动进入视口”场景
如果目标是「当某元素滚动进/出可视区域时执行逻辑」(如懒加载图片、动画触发动画),scroll + 手动计算 getBoundingClientRect() 效率低且易出错。
- 用
IntersectionObserver更简洁可靠:
const io = new IntersectionObserver((entries) => {<br> entries.forEach(entry => {<br> if (entry.isIntersecting) {<br> console.log('元素已进入视口');<br> // 执行懒加载或动画<br> }<br> });<br> }, { threshold: 0.1 }); // 10% 进入时即触发<br> io.observe(document.querySelector('.target')); - 它自动处理滚动、resize、缩放、iframe 内滚动等多种情况,无需手动节流,且性能由浏览器优化
注意事项与最佳实践
实际开发中还需注意以下细节:
-
移除监听器:组件卸载或模块销毁时务必调用
removeEventListener,或使用{ once: true }选项绑定一次性事件 -
移动端兼容性:iOS Safari 中
resize在键盘弹出时不触发,可监听focus/blur配合setTimeout检测高度变化 -
避免同步强制同步布局:在
scroll回调中读取offsetHeight、getBoundingClientRect()等会触发回流,应尽量缓存或延后读取 -
优先用 CSS 解决:比如粘性定位(
position: sticky)、媒体查询(@media)可替代部分 JS resize 逻辑,更高效也更声明式

















