JS监控DOM大小的变化

本文探讨了JS如何监控DOM元素的大小变化,分析了DOM大小变化的诱因和解决办法,包括循环比对和DOM滚动事件驱动两种方案,并对每种方法的性能和适用场景进行了详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS监控DOM大小的变化

由于浏览器原生不自带DOM元素的resize事件,一直想弄一个JS监控DOM元素大小变化的功能,之前想过很多方法都各有优缺点,但是最近发现了一个很不错的方法,虽然以前有设想过但一直没有时间去实现,知道看到有人实现了和我的设想相同的一种比较优良的监听方法,这篇文章将主要讲述两种监听方法,以及优劣分析

DOM大小的变化的诱因

DOM大小的决定因素

在寻找如何监听DOM大小变化的解决办法之前,这里先列举DOM元素宽高的数值的情况:

  1. 自己设置的固定宽高,如样式设置style="width:100px;height:100px"
  2. 由上层元素决定,如样式设置style="width:50%;height:50%"
  3. 有下层元素决定,内容撑大

宽高各自独立,可以混合出现

DOM大小改变的原因

知道了决定的DOM大小的因素的3种情况,可以很快的列出大部分导致DOM大小变化的情况:

  1. 元素修改自己的宽高样式

    元素修改自己的宽高属性,是最简单最直接也是最常见的DOM大小变化的情况

  2. 当元素大小由上层元素(包括窗口大小)决定,上层元素大小发生变动时

  3. 当袁术大小由下层元素决定,下层元素大小发生变动时

上面三种是最常见也是最容易想到的,仔细思考还存在其他情况

  1. 元素的class的内容改变

    当元素的class的内容的宽高变化时,元素是不能监听到class内容变化的,虽然这个操作很不好,但是在切换页面皮肤时确实可能存在这样的问题

  2. 过渡/动画

    当元素设置了过渡/动画效果时,元素的宽高是可能在每一帧的出现变化的

这些因素也是最难以监听和获取的可以叫"隐式变化",之前3个比较简单的变化由于可以在节点树上和style上明显看出变化,可以简单叫"显式变化"

显式变化可以通过MutationObserver监听来达到事件流驱动节约性能的效果,但是隐式变化也是同样需要检测出来的,下面将列举两个检测办法,都各自有优缺点

解决办法

循环比对

最最简单直接有效的方法当然是每一帧去计算和比对需要监听的DOM的大小是否发生改变

height="265" scrolling="no" title="loop-listen" src="//codepen.io/sqchenxiyuan/embed/wYEqmZ/?height=265&theme-id=0&default-tab=html,result" allowfullscreen="true">See the Pen loop-listen by sqchenxiyuan ( @sqchenxiyuan) on CodePen.

这个方法很简单,也能很快的思考出来,但是这个方法很多文章或者人都认为有很大的性能消耗,其实这个方法的性能消耗在现在的计算机性能下是很小的,相较于每次DOM大小变化导致的重绘消耗的性能,占比很小

覆盖情况

100%,所有元素都可以这样监听

性能分析

展示性能分析前,先先说下测试展示用的机器的配置,CPU是interl i7-4790k,GPU为GTX960

前6秒为禁止状态,后6秒为改动大小的状态

100节点监听

在前6秒里,可以看到以js的执行为主(毕竟没有重绘的需求),一直在执行dom大小的变化检测,但占据的性能并不是很多,大约1%。

在后4秒里,由于出现了dom的大小变化,页面还是执行渲染,在这段时间JS(这里JS只执行循环对比)的消耗比例很低,大约2.5%(在正常监听的时候会有一些操作,比例是会明显增大的),反而是渲染会占据近乎2倍的消耗

其他数量的节点的测试数据
节点数 静态总时间 静态JS耗时 动态总时间 动态JS耗时
100 5990ms 84.3ms(1.40%) 4020ms 111.1ms(2.76%)
500 5928ms 204.2ms(3.44%) 4760ms 209.7ms(4.41%)
1000 6007ms 342.9ms(5.71%) 5125ms 342ms(6.67%)
2000 6580ms 654ms(9.94%) 7955ms 1094.9ms(13.76%)
5000 7667ms 1880.6ms(24.53%) 6277ms 823.3ms(13.11%)

根据上面的表可以看到静态和动态的时间大体是跟着监听的DOM节点的数量成正比的,动态消耗的时间比静态略多可能是由于对多一层调用的堆栈导致,在5000节点的时候,动态渲染消耗的时间反而少了,是由于5000节点同时改变大小,消耗了大量的资源进行重绘,这时候的帧数也开始明显降低

这个图可以看到资源几乎被完全利用了,而且大部分是渲染消耗的

小结

这个方法可以覆盖全部的变动情况,而且可以监听所有的DOM对象,但是在监听的节点数量很多的情况下(500+)的时候,消耗的性能就很多了,在这之前的消耗还是在可接受的范围内(<5%)的,而且一般实际上是没有这么多需要监听的节点的~~~,一般都在100节点以内,性能消耗是在1%内的,是一个完全满足需求的方案,没必要为了一个几乎不会遇到的需求而放弃这个简单有效的方案

接下来介绍一下,一个基于事件实现的监听方案

DOM滚动事件驱动方案

这个方案是通过监听DOM元素的scroll事件来进行的,在大家可以在这篇文章了解下详情,这里只简单介绍下原理

scroll事件发生的原理

当一个元素的scrollLeftscrollTop属性发生改变的时候,这个元素就会触发scroll事件,scroll事件是不会冒泡的

父元素大小变化时内部元素的对scrollLeft和scrollTop的影响

当父元素大小变化时,内部的元素(内容)在一定条件下会影响scrollLeftscrollTop的值,从而触发scroll函数,下面都以高度为例(宽度其实相同)

父元素变大

在一般情况下父元素变大是不会影响内容的

但是当父元素变大的幅度超过了剩余的内容,那么内容会跟着底部向下移动,导致scrollTop缩小

这样我们就能监听到scroll事件啦!

height="265" scrolling="no" title="通过scroll监听DOM变大" src="//codepen.io/sqchenxiyuan/embed/gBdKjp/?height=265&theme-id=0&default-tab=html,result" allowfullscreen="true">See the Pen 通过scroll监听DOM变大 by sqchenxiyuan ( @sqchenxiyuan) on CodePen.

父元素变小

放大了的原理理解了话,那么放小来看下

如果是父元素变小,如果内容的大小不变,那么scrollTop永远都不会发生变化,为了让其发生变化,这里需要让子元素能跟着父元素变化,而且必须变化幅度大于父元素,才能促使父元素由于超过展示范围,去改变scrollTop

当内容的高度是父元素的100%以上时,由于速度比父元素缩小的块,导致父元素必须修改scrollTop来达到允许的最大的scrollTop,通过这个原理我们就可以监听到父元素的缩小啦!!!

在代码中最好使用200%及其以上,因为在浏览器中DOM的宽高都是整数的,如果是用200%一下会导致收缩不明显,而会漏掉一部分

height="265" scrolling="no" title="通过scroll监听DOM的缩小" src="//codepen.io/sqchenxiyuan/embed/mzGjqR/?height=265&theme-id=0&default-tab=html,result" allowfullscreen="true">See the Pen 通过scroll监听DOM的缩小 by sqchenxiyuan ( @sqchenxiyuan) on CodePen.

放大放小混合

上面两种方法都只能实现一直情况,但是只要混合一下就能实现同时监听放大缩小了,一方变动的同时需要恢复scrollTop来让另一方也能跟上

height="297" scrolling="no" title="通过Scroll监听元素大小变化" src="//codepen.io/sqchenxiyuan/embed/GYXXoQ/?height=297&theme-id=0&default-tab=html,result" allowfullscreen="true">See the Pen 通过Scroll监听元素大小变化 by sqchenxiyuan ( @sqchenxiyuan) on CodePen.

性能分析

这个相较于循环比对方法要复杂的多,但是节约了DOM没有变动时(大部分情况)的计算性能,使用事件驱动是必然性能优化很多的

小结

这个方法虽然很好的解决了循环比对的性能问题,但是会污染使用者的DOM树结构(添加了额外的东西),而且必须要求DOM能够在下面插入节点,文本(TextNode)、图片(ImageElement)等等这些元素就么法了,同时需要父元素是一个相对元素,不然就不能模拟捕捉父元素的宽高,限制条件还是很多的

封装源码

循环监听

class DomResizeWatcher{
   
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值