Dropdown下拉菜单开发避坑指南与性能优化实践

萌新.风云 组件 阅读 2,863
赞 17 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

最近接手了一个老项目,里面的Dropdown下拉菜单真是把我折磨得够呛。一打开页面,点开下拉菜单,整个页面就卡得像老牛拉破车一样。特别是在列表项超过50条的时候,简直没法用。我试了几次,发现加载时间平均要5秒多,这谁受得了啊。

Dropdown下拉菜单开发避坑指南与性能优化实践

找到瓶颈了!

开始我以为是后端接口的问题,结果发现数据早就返回了,问题出在前端渲染上。我用Chrome的Performance工具跑了一下,发现问题主要集中在两个地方:

  • 每次点击展开时都会重新创建DOM节点
  • 大量使用了复杂的CSS动画效果

尤其是那个动画效果,看着挺炫酷,但实际上就是性能杀手。

优化思路:虚拟滚动+简化动画

试了几种方案,最后觉得虚拟滚动最靠谱。其实原理很简单:只渲染用户能看到的部分,其他都隐藏起来。说干就干,下面是优化前后的代码对比。

优化前的代码

原来的代码简单粗暴,直接把所有选项都渲染出来:

function renderDropdown(items) {
  const dropdown = document.getElementById('dropdown');
  items.forEach(item => {
    const div = document.createElement('div');
    div.className = 'dropdown-item';
    div.textContent = item.label;
    dropdown.appendChild(div);
  });
}

看起来没啥问题,但当items数量一大,DOM节点瞬间爆炸。

优化后的代码

改用虚拟滚动后,代码复杂了一些,但性能提升非常明显:

class VirtualScroll {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(window.innerHeight / itemHeight);
    this.startIndex = 0;
    this.endIndex = this.visibleCount;
    this.render();
  }

  render() {
    this.container.innerHTML = '';
    for (let i = this.startIndex; i < this.endIndex; i++) {
      const div = document.createElement('div');
      div.className = 'dropdown-item';
      div.style.position = 'absolute';
      div.style.top = ${i * this.itemHeight}px;
      div.textContent = this.items[i].label;
      this.container.appendChild(div);
    }
  }

  update(scrollTop) {
    const newStartIndex = Math.floor(scrollTop / this.itemHeight);
    const newEndIndex = newStartIndex + this.visibleCount;
    
    if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
      this.startIndex = newStartIndex;
      this.endIndex = newEndIndex;
      this.render();
    }
  }
}

// 使用方式
const dropdown = document.getElementById('dropdown');
const items = Array.from({length: 1000}, (_, i) => ({label: Item ${i+1}}));
const virtualScroll = new VirtualScroll(dropdown, items, 40);

dropdown.addEventListener('scroll', () => {
  virtualScroll.update(dropdown.scrollTop);
});

动画效果的简化

原来的动画用了好几个transform和opacity的组合,改成了简单的height过渡:

.dropdown-content {
  overflow: hidden;
  transition: height 0.3s ease-in-out;
}

.dropdown-content.open {
  height: auto;
}

性能数据对比

优化完一测,效果立竿见影:

  • 初始渲染时间从5秒降到800毫秒
  • 滚动时的FPS从15帧提升到55帧
  • 内存占用减少了60%

特别是在长列表的情况下,用户体验提升特别明显。以前滚动到底部都要等半天,现在丝滑得很。

还有个小插曲

中间踩了个坑,忘记处理滚动位置的边界情况,导致有时候内容会闪一下。后来加了个判断才搞定:

if (newStartIndex < 0) newStartIndex = 0;
if (newEndIndex > this.items.length) newEndIndex = this.items.length;

这种小问题最容易被忽略,但又直接影响体验,真是不得不防。

结尾:以上是我的优化经验

这次优化让我深刻体会到,有时候看似高大上的功能,反而可能是性能杀手。虽然最终方案不是最完美的,但确实是最实用的。如果你有更好的实现方法,欢迎在评论区交流。

对了,这个项目里还遇到个有趣的事儿,关于事件委托的优化,下次再跟大家分享。

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

暂无评论