还在用 scroll 事件做懒加载?这三个浏览器原生 API 更高效
你还在用 scroll 事件做懒加载?用 setInterval 检测元素变化?用 window.resize 监听尺寸?
这些老方法不是不能用,只是太笨重——性能差、代码乱、容易翻车。
浏览器早就内置了三把“手术刀”:IntersectionObserver、ResizeObserver、MutationObserver,精准、高效、零依赖。今天一篇文章全搞懂。
一、IntersectionObserver:元素进入视口了吗?
经典场景:图片懒加载
以前大家这么写:
window.addEventListener('scroll', () => {
document.querySelectorAll('img[>).forEach(img => {
const rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
}
})
})scroll 事件每秒触发几十次,每次都要调用 getBoundingClientRect(),强迫浏览器重新计算布局,卡到怀疑人生。
换成 IntersectionObserver:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img) // 加载完就取消观察
}
})
})
document.querySelectorAll('img[>).forEach(img => {
observer.observe(img)
})浏览器内部处理,不占主线程,性能天壤之别。
进阶用法:控制触发时机
const observer = new IntersectionObserver(callback, {
root: null, // null = 视口
rootMargin: '0px 0px -100px 0px', // 距离底部100px时就触发
threshold: [0, 0.5, 1] // 0%、50%、100% 可见时分别触发
})threshold: 0.5 表示元素有 50% 进入视口才触发,适合做“阅读进度”统计。
实战:滚动动画触发
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in')
}
})
}, { threshold: 0.15 })
document.querySelectorAll('.card').forEach(el => observer.observe(el))兼容性:Chrome 51+ | Firefox 55+ | Safari 12.1+ | 大部分现代浏览器已全面支持。
二、ResizeObserver:元素尺寸变了
window.resize 只能监听窗口,而且拿不到单个元素的尺寸变化。ResizeObserver 直接观察目标元素本身。
基础用法
const observer = new ResizeObserver((entries) => {
entries.forEach(entry => {
const { width, height } = entry.contentRect
console.log(`元素新尺寸:${width} x ${height}`)
})
})
observer.observe(document.querySelector('.box'))实战:响应式 Canvas 自适应
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect
canvas.width = width * devicePixelRatio
canvas.height = height * devicePixelRatio
ctx.scale(devicePixelRatio, devicePixelRatio)
redraw() // 重新绘制内容
}
})
resizeObserver.observe(canvas)再也不用等 window.resize,父容器变化、侧边栏收起,Canvas 立刻跟着响应。
实战:文字溢出自动缩小字号
function fitText(el) {
const observer = new ResizeObserver(() => {
let fontSize = 24
el.style.fontSize = fontSize + 'px'
while (el.scrollWidth > el.clientWidth && fontSize > 10) {
fontSize -= 1
el.style.fontSize = fontSize + 'px'
}
})
observer.observe(el)
}兼容性:Chrome 64+ | Firefox 69+ | Safari 13.1+ | 现代浏览器均已支持,需要兼容旧版可考虑 polyfill。
三、MutationObserver:DOM 变了
监听 DOM 的增删改,是三个里面最“底层”的一个,框架内部大量使用。
基础用法
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
console.log('变化类型:', mutation.type)
console.log('新增节点:', mutation.addedNodes)
console.log('删除节点:', mutation.removedNodes)
console.log('属性变化:', mutation.attributeName)
})
})
observer.observe(document.querySelector('#app'), {
childList: true, // 监听子节点增删
subtree: true, // 监听所有后代
attributes: true, // 监听属性变化
characterData: true // 监听文本内容变化
})实战:第三方内容注入监控
有时候页面嵌入广告或第三方脚本,会偷偷往 body 里插东西:
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.tagName === 'IFRAME') {
console.warn('检测到非预期 iframe 注入:', node)
node.remove() // 直接移除
}
})
})
})
observer.observe(document.body, { childList: true, subtree: true })实战:监听动态加载的元素
SPA 场景中,路由跳转后某个元素才出现,用 querySelector 拿不到,可以这样等:
function waitForElement(selector) {
return new Promise(resolve => {
const existing = document.querySelector(selector)
if (existing) return resolve(existing)
const observer = new MutationObserver(() => {
const el = document.querySelector(selector)
if (el) {
resolve(el)
observer.disconnect()
}
})
observer.observe(document.body, { childList: true, subtree: true })
})
}
// 使用
const el = await waitForElement('.dynamic-button')
el.click()兼容性:Chrome 26+ | Firefox 14+ | Safari 7+ | 几乎所有现代浏览器都支持。
四、三者对比速查
| API | 监听什么 | 典型场景 |
|---|---|---|
| IntersectionObserver | 元素是否进入视口 | 懒加载、滚动动画、曝光统计 |
| ResizeObserver | 元素尺寸变化 | 响应式图表、Canvas 自适应 |
| MutationObserver | DOM 结构/属性/文本变化 | 动态内容监控、等待元素出现 |
五、记住一个通用模式
三个 Observer 的 API 设计高度一致,记住这套模式就全会了:
// 1. 创建观察者
const observer = new XxxObserver((entries) => {
entries.forEach(entry => {
// 处理每一条变化记录
})
})
// 2. 开始观察
observer.observe(targetElement, options)
// 3. 停止观察(别忘了清理!)
observer.unobserve(targetElement) // 取消某个元素
observer.disconnect() // 全部取消组件卸载时记得调用 disconnect(),否则观察者会持有元素引用,导致内存泄漏。
用这三个 API 替换掉你项目里的 scroll 节流、setInterval 轮询、手动 getBoundingClientRect,代码立刻清爽一个档次。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!