推荐用标志位+requestAnimationFrame处理scroll事件,因其自动对齐60fps渲染节奏、后台暂停省资源;需配合passive: true缓解卡顿,但不能替代节流;判断滚动到底部应设阈值而非等式;DOM插入须避免innerHTML+=,改用append或insertAdjacentHTML。

scroll 事件不加节流或防抖,页面大概率会卡顿、掉帧、CPU 占用飙升——这不是配置问题,是浏览器底层渲染机制决定的。
scroll 该用 throttle 还是 requestAnimationFrame
节流函数如 throttle(handleScroll, 100) 能控频次,但仍有丢帧风险:若逻辑耗时 >16ms,仍可能挤占渲染帧。而 requestAnimationFrame 自动对齐浏览器刷新节奏(通常 60fps),且在标签页后台时自动暂停,更省资源。
推荐组合写法:用标志位 + requestAnimationFrame,避免重复注册帧回调:
let ticking = false;
function handleScroll() {
if (!ticking) {
requestAnimationFrame(() => {
updatePosition(); // 实际滚动逻辑
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', handleScroll);- 不用
setTimeout或setInterval模拟节流,它们不感知渲染帧 - 别在
requestAnimationFrame回调里再调用requestAnimationFrame,否则容易失控 - 如果逻辑含 DOM 查询(如
getBoundingClientRect),确保它在帧内执行,而非在 scroll 触发瞬间就查
passive: true 能不能直接解决 scroll 卡顿
能缓解,但不是万能解药——它只解决「浏览器等待 JS 回调结束才滚动」的问题,不减少回调执行次数。加上 { passive: true } 后,preventDefault() 不再生效,滚动更顺滑,但你的 handler 还是会被高频调用。
立即学习“前端免费学习笔记(深入)”;
必须和 requestAnimationFrame 或节流配合使用,否则 CPU 依然吃紧。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 旧版 iOS Safari 对
passive支持不全,需 feature detect 或降级处理 - 若你确实需要
preventDefault()(比如自定义滚动容器),必须显式设{ passive: false } - 现代框架(如 React)中,事件委托到 document 时,
passive选项可能被忽略,建议绑定到具体目标元素
为什么不能只靠 window.scrollY + window.innerHeight === document.body.scrollHeight 判断底部
这个等式在很多浏览器(尤其是 iOS Safari)里因渲染延迟、缩放、字体加载、CSS calc 表达式重排等原因,经常不成立。直接等号判断会导致无限滚动失效或误触发。
稳妥做法是留一个余量(比如 100px),用「距离底部小于等于该值」作为触发条件:
const threshold = 100; const isAtBottom = window.scrollY + window.innerHeight >= document.body.scrollHeight - threshold;
- 统一用
document.body.scrollHeight,别用document.documentElement.scrollHeight(IE 和部分移动端行为不一致) - 首次加载完内容后,
scrollHeight会变大,但新 DOM 插入后需确保重排完成再判断,否则可能误判 - 如果页面有 sticky header 或 fixed footer,记得把它们的 offset 从
threshold中排除,否则触发点会偏移
fetch 加载后怎么安全插入 DOM
直接 innerHTML += 会清空已有绑定的事件监听器、销毁已挂载的 React/Vue 组件,还可能引发内存泄漏。必须用 append() 或 insertAdjacentElement()。
假设你用 fetch 拿到的是 JSON 数组,渲染成列表:
const listEl = document.getElementById('item-list');
data.forEach(item => {
const li = document.createElement('li');
li.textContent = item.title;
listEl.append(li);
});- 避免在每次插入前清空整个
listEl,否则所有子节点事件监听器丢失 - 如果服务端返回的是完整 HTML 字符串,用
insertAdjacentHTML('beforeend', htmlStr)更高效,但要确保内容可信(防 XSS) - iOS 需注意:软键盘弹出/收起也会触发
resize和scroll,建议对无限滚动加isLoading开关,并在finally中清除
真正难的不是写节流函数,而是判断哪些操作必须放在 requestAnimationFrame 里、哪些可以延迟到下一轮 microtask、哪些压根不该在 scroll 里做——比如 layout trashing(反复读写 offsetTop/scrollHeight)、同步 DOM 查询、未压缩的大图插入。这些细节不处理,节流只是把卡顿从“明显”变成“偶尔”。


















