阻止冒泡的正确姿势与常见误区解析

UX慧青 移动 阅读 2,549
赞 21 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

最近接手了一个移动端项目,功能其实不复杂,就是个带列表的页面。但问题来了,滚动的时候卡得受不了,尤其是快速滑动时,整个页面就像被冻住了一样。试了几次发现,只要手指在屏幕上滑动,界面就会变得特别迟钝,完全没法正常使用。

阻止冒泡的正确姿势与常见误区解析

一开始我还以为是CSS动画的问题,毕竟之前遇到过类似情况。但把所有transition和transform都注释掉后,问题依旧存在。折腾了半天才发现,罪魁祸首居然是事件冒泡!没错,就是那个看似无害的touchmove事件。

找到瓶颈了!

为了确认是不是事件冒泡导致的问题,我打开了Chrome DevTools的Performance面板,模拟了移动设备环境,然后录了一段操作视频。结果让我大吃一惊:每次触发touchmove,事件居然从子元素一路冒泡到了document级别!更糟糕的是,页面上绑定了好几个全局监听器,这些监听器都在处理冒泡事件,导致性能急剧下降。

简单算了一下,假设一个列表有50个子项,每个子项都有自己的事件监听器,再加上几个全局监听器,每秒触发几十次事件,CPU占用率直接飙到80%以上。这谁顶得住啊!

优化方案:阻止冒泡

发现问题后,我试了几种方案,最后这个效果最好:

核心思路就是尽早阻止事件冒泡,不让它扩散到不必要的父级或全局监听器。具体做法如下:

优化前代码

document.addEventListener('touchmove', function(e) {
    console.log('Document level touchmove');
});

const items = document.querySelectorAll('.list-item');
items.forEach(item => {
    item.addEventListener('touchmove', function(e) {
        console.log('Item level touchmove');
    });
});

这段代码的问题很明显:touchmove事件会先在子元素触发,然后冒泡到document级别。即使我们只关心子元素的行为,全局监听器依然会被调用,白白消耗性能。

优化后代码

document.addEventListener('touchmove', function(e) {
    console.log('Document level touchmove');
}, { passive: true }); // 使用passive提高滚动性能

const items = document.querySelectorAll('.list-item');
items.forEach(item => {
    item.addEventListener('touchmove', function(e) {
        e.stopPropagation(); // 阻止事件冒泡
        console.log('Item level touchmove');
    }, { passive: true });
});

这里有两个关键改动:

  • 阻止冒泡:在子元素的事件处理函数里调用了e.stopPropagation(),确保事件不会传播到父级或全局监听器。
  • 使用Passive Listener:通过{ passive: true }选项告诉浏览器,这个事件监听器不会调用e.preventDefault(),从而允许浏览器优化滚动性能。

踩坑提醒:千万别忘了passive选项!如果没加,浏览器会默认认为你可能会调用e.preventDefault(),这就可能导致滚动延迟。我之前就因为漏了这个选项,优化效果大打折扣。

优化后:流畅多了

改完代码后,再次用Performance面板测试了一下。结果相当满意:原本每次滚动都会触发上百次事件回调,现在只会在子元素内部触发,全局监听器几乎没动静了。

来看下数据对比:

  • 优化前:每秒触发事件约120次,页面滚动FPS只有15左右,卡顿严重。
  • 优化后:每秒触发事件降到30次以下,滚动FPS稳定在50以上,基本感觉不到卡顿。

另外,我还特意测了一下CPU占用率:

  • 优化前:滚动时CPU占用率高达80%-90%。
  • 优化后:滚动时CPU占用率降到了30%-40%,流畅度提升非常明显。

还有一点值得一提,我在页面初始化时用fetch('https://jztheme.com/api/data')加载了一些数据,优化后数据加载完成后的滚动体验也提升了不少,算是意外收获吧。

性能数据对比

虽然数据已经提到了一些,但我觉得还是有必要再强调一下具体的优化效果。以下是优化前后的主要指标对比:

  • 事件触发次数:从每秒120次降到30次。
  • 滚动FPS:从15提升到50+。
  • CPU占用率:从80%-90%降到30%-40%。
  • 用户感知:从“卡得想砸手机”变成“还算流畅”。

当然,这个方案也不是完美的。比如,如果你的项目中确实需要某些全局事件来处理逻辑,那可能需要额外的判断条件,避免误杀必要的事件。不过整体来说,这种局部阻止冒泡的方式在大多数场景下都能显著提升性能。

踩坑提醒:这三点一定注意

最后总结一下我在优化过程中踩过的坑,希望你能少走弯路:

  • 别忘了Passive Listener:没有{ passive: true },性能优化效果会大打折扣。
  • 不要滥用e.stopPropagation():如果全局事件真的需要,记得加条件判断。
  • 测试真实设备:模拟器上的表现可能和真机有差异,建议多用几台手机测一下。

以上是我个人对这个阻止冒泡性能优化的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续我会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论