窗口大小变化导致页面卡顿?试试这几个办法
很多人做响应式页面的时候,都会写这样一行代码:
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再算
做到这几条,页面就不会因为窗口大小变化卡得跟幻灯片似的了。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!