还在用 scroll 事件做懒加载?这三个浏览器原生 API 更高效

更新日期: 2026-03-27 阅读: 38 标签: 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 自适应
MutationObserverDOM 结构/属性/文本变化动态内容监控、等待元素出现

五、记住一个通用模式

三个 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,代码立刻清爽一个档次。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13516

相关推荐

适合写api接口文档的管理工具有哪些?

现在越来越流行前后端分离开发,使用ajax交互。所以api接口文档就变的十分有意义了,目前市场有哪些比较优秀的接口文档管理工具呢?比如:MinDoc,eoLinker,apizza,RAML,Swagger等等

前后端分离,如何防止api接口被恶意调用或攻击

无论网站,还是App目前基本都是基于api接口模式的开发,那么api的安全就尤为重要了。目前攻击最常见的就是“短信轰炸机”,由于短信接口验证是App,网站检验用户手机号最真实的途径,使用短信验证码在提供便利的同时,也成了呗恶意攻击的对象,那么如何才能防止被恶意调用呢?

超赞的腾讯短网址(微信url.cn短链接)生成api接口

腾讯短网址的应用场景很广,譬如短信营销、邮件推广、微信营销、QQ营销、自媒体推广、渠道推广等,都会用到短网址。究其原因是在于短网址可以降低推广成本、用户记忆成本,提高用户点击率;在特定的场景下推广还能规避关键词,防止域名被拦截

JSON API免费接口_ 免费的天气预报、地图、IP、手机信息查询、翻译、新闻等api接口

整理提供最新的各种免费JSON接口,其中有部分需要用JSONP调用。方面前端同学的学习或在网站中的使用,包括:免费的天气预报、地图、IP、手机信息查询、翻译、新闻等api接口

ACE Editor在线代码编辑器的API使用文档

ACE 是一个开源的、独立的、基于浏览器的代码编辑器,可以嵌入到任何web页面或JavaScript应用程序中。ACE支持超过60种语言语法高亮,并能够处理代码多达400万行的大型文档

浏览器中的图像识别 API

在 Native 开发中,Android 和 IOS 平台都在系统层面直接提供给了应用开发识别图像的一些能力,比如对于二维码/条形码的识别,Android 可以使用 barcode API 、 iOS 可以使用 CIQRCodeFeature API 。

Vue 3.0 体验 Vue Function API

近 Vue 官方公布了 Vue 3.0 最重要的RFC:Function-based component API,并发布了兼容 Vue 2.0 版本的 plugin:vue-function-api,可用于提前体验 Vue 3.0 版本的 Function-based component API

vue-next 函数式 api

在分享 vue-next 各个子模块的实现之前,我觉的有必要比较全面的整理下 vue-next 中提出的函数式 api,了解这些的话,无论是对于源码的阅读,还是当正式版发布时开始学习,应该都会有起到一定的辅助作用

在原生 React Native 应用中使用 Expo API

你可以在任何 React Native 应用程序中使用尽可能少或尽可能多的 Expo SDK。 我们已经花了很多时间构建和维护这些包含原生应用特性的跨平台 API,我们很高兴最终实现了向整个 React Native 生态共享这些 API

构建API的最佳编程语言是什么?

你是否正在设计第一个Web应用程序?也许你过去已经建立了一些,但是目前也正在寻找语言的变化以提高你的技能,或尝试新的东西。有了所有信息,就很难决定为下一个产品或项目选择哪种编程语言。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!