实现流畅Accordion手风琴组件的那些坑与技巧总结

UX佳丽 组件 阅读 2,081
赞 38 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

最近在做一个带手风琴效果的项目,原本觉得就是个简单的交互组件,结果上线后发现性能差到离谱。特别是在低配安卓机上,切换面板的时候卡得像PPT,完全没法用。最夸张的是有用户反馈说整个页面都跟着抖动,差点以为是中病毒了。

实现流畅Accordion手风琴组件的那些坑与技巧总结

当时我那个崩溃啊,明明代码写得挺规范的,怎么就性能这么差呢?尤其是当手风琴项超过10个时,展开收缩动画简直惨不忍睹,CPU占用直接飙到90%以上。

找到瓶颈了!

为了搞清楚问题到底出在哪,我试了好几种工具来排查。Chrome DevTools 的 Performance 面板帮了大忙,发现主要问题集中在两个地方:

  • 布局抖动:每次切换都会触发强制重排(reflow)
  • 过度依赖CSS动画:height属性的变化让浏览器频繁计算布局

另外我还用 Lighthouse 跑了一下性能测试,初始渲染时间居然要5秒多,这谁受得了啊。

核心优化:从JS和CSS下手

试了几种方案后,最后这个效果最好:改用transform实现动画,配合requestAnimationFrame优化动画帧率。

先看看优化前的代码有多糟糕:

// 优化前的代码
function toggleAccordion(index) {
  const items = document.querySelectorAll('.accordion-item');
  items.forEach((item, i) => {
    if (i === index) {
      item.style.height = item.scrollHeight + 'px';
      item.classList.add('active');
    } else {
      item.style.height = '0px';
      item.classList.remove('active');
    }
  });
}

这里的问题很明显:每次都要读取scrollHeight,然后直接修改height样式,这种做法会让浏览器不停计算布局。更别说还用了querySelectorAll这种重型操作。

优化后的代码:

// 优化后的代码
const accordionItems = Array.from(document.querySelectorAll('.accordion-item'));

function toggleAccordion(index) {
  accordionItems.forEach((item, i) => {
    if (i === index) {
      expandItem(item);
    } else {
      collapseItem(item);
    }
  });
}

function expandItem(item) {
  requestAnimationFrame(() => {
    item.style.display = 'block';
    const height = item.scrollHeight;
    
    requestAnimationFrame(() => {
      item.style.transition = 'transform 300ms ease';
      item.style.transform = scaleY(1);
    });
  });
}

function collapseItem(item) {
  item.style.transition = 'transform 300ms ease';
  item.style.transform = scaleY(0);
  
  setTimeout(() => {
    item.style.display = 'none';
  }, 300);
}

具体改动点

这里重点说下几个关键改动:

  1. 用transform替代height:scaleY比直接改height性能好太多,因为不会触发layout重排
  2. 双层RAF:第一层确保样式变更被应用,第二层再启动动画,这样能避免布局抖动
  3. 缓存DOM节点:把querySelectorAll的结果存起来,避免每次都重新查询
  4. 提前计算高度:在动画开始前就获取好高度值,避免在动画过程中反复计算

还有一个小技巧,我把display:none改成visibility:hidden配合绝对定位,这样可以保持元素在文档流中的占位,避免影响其他元素布局。

性能数据对比

优化完跑了一遍测试,数据提升相当明显:

  • 首屏加载时间:从5.2秒降到800毫秒
  • 切换动画帧率:从平均15fps提升到稳定60fps
  • CPU占用率:从90%降到30%左右
  • 内存使用:减少了40%

特别惊喜的是,在低端安卓机上的表现也相当流畅了,基本感觉不到卡顿。虽然还是有极个别老旧设备会有点迟滞,但相比之前的惨状已经好太多了。

踩坑提醒

有几个坑我必须提一下,折腾了半天才发现:

  1. 别忘了给父容器加上overflow:hidden,否则scaleY(0)的时候内容可能会溢出
  2. transition一定要在动画开始前设置好,不能动态添加,否则会有闪屏
  3. 记得处理好aria-expanded属性,无障碍支持很重要

还有个小插曲,刚开始我把所有动画都放到了GPU上,结果发现有些复杂页面会出现闪烁。后来调整了will-change的使用范围才解决这个问题。

优化后:流畅多了

现在这个手风琴组件算是比较理想了,不管是PC还是移动端都能流畅运行。不过说实话,这个方案也不是完美的,比如首次渲染还是有点耗时,但我发现这个跟我们项目里用的某个第三方库有关,准备后续再单独优化。

以上是我个人对这个手风琴组件性能优化的完整讲解,有更优的实现方式欢迎评论区交流。这个组件的其他特性优化我还在继续研究,后续会继续分享这类博客。

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

暂无评论