窗口大小变化导致页面卡顿?试试这几个办法

摘要:窗口大小一变,resize事件会不停地触发。你鼠标拖一下窗口,一秒钟能触发几十次。每次触发,你写的代码就会跑一遍。如果里面有操作DOM或者计算位置的代码,浏览器就得重新算样式、重新画页面。这就是重排。

很多人做响应式页面的时候,都会写这样一行代码:

window.addEventListener('resize', () => {
  // 做点什么
})

这个写法本身没毛病。但是页面一卡,问题就来了。


为什么会卡

窗口大小一变,resize事件会不停地触发。你鼠标拖一下窗口,一秒钟能触发几十次。

每次触发,你写的代码就会跑一遍。如果里面有操作DOM或者计算位置的代码,浏览器就得重新算样式、重新画页面。这就是重排。

重排一次还好,一秒来几十次,页面就抖得不行了。低配手机直接卡死。


一个会出问题的例子

window.addEventListener('resize', () => {
  const width = window.innerWidth
  if (width < 768) {
    document.body.classList.add('mobile')
  } else {
    document.body.classList.remove('mobile')
  }
})

这段代码看起来挺简单。但是窗口一拖动,这个函数一秒能跑几十次。每次都增删class,浏览器就得重新算样式。页面就会一卡一卡的。


用防抖来解决

防抖的意思:你一直动窗口,我就不干活。等你停下来了,我再干。

let timer = null

window.addEventListener('resize', () => {
  clearTimeout(timer)
  timer = setTimeout(() => {
    const width = window.innerWidth
    if (width < 768) {
      document.body.classList.add('mobile')
    } else {
      document.body.classList.remove('mobile')
    }
  }, 200)
})

你拖动窗口的时候,每次都会把上一个定时器清掉。等你停下来200毫秒之后,才真正执行代码。

好处:窗口怎么拖都不会卡。坏处:你要等到松手之后才会变化。


用节流让变化更顺滑

节流的意思:你一直动窗口,我每隔一段时间干一次活。不多干也不少干。

let canRun = true

window.addEventListener('resize', () => {
  if (!canRun) return
  canRun = false
  setTimeout(() => {
    const width = window.innerWidth
    if (width < 768) {
      document.body.classList.add('mobile')
    } else {
      document.body.classList.remove('mobile')
    }
    canRun = true
  }, 100)
})

不管你拖得多快,每100毫秒最多执行一次。页面不会卡,变化也不会太延迟。


防抖和节流怎么选

  • 如果你只关心窗口最后停在多大,用防抖。比如根据窗口宽度重新排版。

  • 如果你想在拖动过程中也要跟着变,但不要太频繁,用节流。比如跟着窗口大小调整某个元素的位置。

大部分响应式页面用防抖就够了。用户拖动窗口的时候不会一直盯着看,停下来再变也没问题。


一个更简单的办法:不用JS

很多需求其实根本不用监听resize。

比如你想在不同窗口大小下显示不一样的样式,用CSS媒体查询就行。

.mobile {
  display: none;
}

@media (max-width: 768px) {
  .desktop {
    display: none;
  }
  .mobile {
    display: block;
  }
}

这个完全不会卡。浏览器自己会处理,比JS快多了。

再比如你要算一个元素的位置,可以考虑用CSS的clamp()、vw、vh这些单位。很多动态布局都能直接用CSS搞定。


记得清理监听

在Vue或者React里面,加了resize监听,页面销毁的时候要记得拿掉。不然切到别的页面,监听还在,会出奇怪的问题。

Vue写法:

export default {
  mounted() {
    this.handleResize = () => {
      // 你的代码
    }
    window.addEventListener('resize', this.handleResize)
  },
  beforeUnmount() {
    window.removeEventListener('resize', this.handleResize)
  }
}

React写法:

useEffect(() => {
  const handleResize = () => {
    // 你的代码
  }
  window.addEventListener('resize', handleResize)
  return () => window.removeEventListener('resize', handleResize)
}, [])


高级办法:用ResizeObserver

窗口大小变了,你想知道某个元素的大小变成什么样了。这时候用resize监听窗口再算元素位置,很容易出问题。

ResizeObserver可以直接监听元素本身的大小变化。

const observer = new ResizeObserver((entries) => {
  const width = entries[0].contentRect.width
  if (width < 300) {
    // 做点事情
  }
})

observer.observe(document.querySelector('.box'))

这个比监听窗口好用。它只在元素大小真正变了的时候才触发,不会乱触发。性能也比resize好。

不过要注意,有些老浏览器不支持。你得看看你的用户用什么浏览器。可以用caniuse查一下兼容性。


总结

核心就这几条:

  • 能不用JS就不用JS,媒体查询优先

  • 非要用监听,加上防抖或者节流

  • 用完了记得清理监听

  • 监听元素大小用ResizeObserver,别用窗口resize再算

做到这几条,页面就不会因为窗口大小变化卡得跟幻灯片似的了。

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

链接: https://shenqiku.cn/article/FLY_13715