实现丝滑Tab切换效果的几种前端技术与踩坑经验分享

俊娜 Dev 前端 阅读 2,428
赞 45 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

前段时间接了个需求,需要做一个多Tab切换的页面,用来展示不同分类的数据。这类需求其实挺常见的,但这次有点特殊:数据量比较大,每个Tab下的内容都需要动态加载,并且交互要求还挺高。一开始我觉得这不就是个普通的Tab切换吗,应该没啥难度,结果还是踩了不少坑。

实现丝滑Tab切换效果的几种前端技术与踩坑经验分享

技术选型这块我纠结了一下。本来想着直接用现成的UI组件库,比如Element Plus或者Ant Design,毕竟它们都有现成的Tab组件,功能也很强大。但后来发现这些组件库对动态加载的支持不够灵活,尤其是当数据量大的时候,性能问题会比较明显。再加上设计稿里有些自定义的样式和动画效果,用现成的组件改起来反而麻烦。所以最后决定自己手写一个Tab切换逻辑。

核心代码就这几行

先简单说下实现思路吧。整个Tab切换的核心其实就是一个状态管理的问题:通过一个变量来控制当前显示的Tab,然后根据这个变量渲染对应的内容。代码看起来是这样的:

<div class="tab-container">
  <div class="tab-header">
    <button 
      v-for="(tab, index) in tabs" 
      :key="index" 
      :class="{ active: currentIndex === index }" 
      @click="switchTab(index)"
    >
      {{ tab.title }}
    </button>
  </div>
  <div class="tab-content">
    <div v-if="currentIndex === index" v-for="(tab, index) in tabs" :key="index">
      {{ tab.content }}
    </div>
  </div>
</div>
export default {
  data() {
    return {
      currentIndex: 0,
      tabs: [
        { title: 'Tab 1', content: '这是第一个Tab的内容' },
        { title: 'Tab 2', content: '这是第二个Tab的内容' },
        { title: 'Tab 3', content: '这是第三个Tab的内容' }
      ]
    };
  },
  methods: {
    switchTab(index) {
      this.currentIndex = index;
    }
  }
};

看起来很简单对吧?确实,基本的功能就这么几行代码就能搞定。但我当时为了追求更好的用户体验,加了一些额外的功能,比如动态加载、懒加载、还有滑动切换的效果,这就让事情变得复杂了。

最大的坑:性能问题

刚开始做的时候,我直接把所有Tab的内容都一次性加载出来了。结果测试的时候发现,当数据量稍微大一点,页面就会卡得不行。尤其是移动端,滑动的时候能明显感觉到掉帧。

折腾了半天才发现,问题出在DOM渲染上。虽然Vue有虚拟DOM优化,但如果一次性渲染太多内容,还是会占用大量内存。于是我想到了懒加载的方案:只有在切换到某个Tab的时候,才去加载它的内容。

调整后的代码是这样的:

export default {
  data() {
    return {
      currentIndex: 0,
      tabs: [
        { title: 'Tab 1', content: null, loaded: false },
        { title: 'Tab 2', content: null, loaded: false },
        { title: 'Tab 3', content: null, loaded: false }
      ]
    };
  },
  methods: {
    switchTab(index) {
      const tab = this.tabs[index];
      if (!tab.loaded) {
        // 模拟异步加载数据
        setTimeout(() => {
          tab.content = 这是第${index + 1}个Tab的内容;
          tab.loaded = true;
        }, 500);
      }
      this.currentIndex = index;
    }
  }
};

这样改完后,性能确实提升了不少。不过又遇到了一个新的问题:用户快速切换Tab的时候,会出现内容闪烁的情况。因为每次切换都会触发异步加载,而加载完成之前页面是空白的。这个问题我后来通过加了一个loading状态来解决:

<div v-if="!tab.loaded && currentIndex === index">加载中...</div>
<div v-else>{{ tab.content }}</div>

虽然解决了问题,但总觉得这个方案还不够优雅。如果有更好的方法,欢迎评论区交流。

又踩坑了,touchmove滚动失效

除了性能问题,还有一个让我头疼的地方是移动端的手势支持。设计稿里要求支持左右滑动切换Tab,这个功能我一开始觉得挺简单的,直接用touchstarttouchend监听手势就行了。结果实际开发的时候发现,滑动的时候页面的滚动事件会被阻断。

查了一圈资料,发现是因为我在touchmove事件里调用了event.preventDefault(),导致浏览器默认的滚动行为被阻止了。后来改成了只在特定条件下调用preventDefault,才勉强解决了这个问题:

let startX = 0;
let moveX = 0;

document.addEventListener('touchstart', (e) => {
  startX = e.touches[0].pageX;
});

document.addEventListener('touchmove', (e) => {
  moveX = e.touches[0].pageX;
  if (Math.abs(moveX - startX) > 50) {
    e.preventDefault();
  }
});

虽然最终效果还可以,但总觉得这个解决方案有点hacky,可能还有优化空间。

回顾与反思

整体来说,这个Tab切换功能算是完成了,但也留下了一些遗憾:

  • 性能方面虽然用了懒加载,但还是感觉可以再优化一下,比如结合Intersection Observer API来做更精细的加载控制。
  • 滑动切换的手势支持还不够流畅,特别是在低端设备上。
  • 代码结构有点乱,尤其是事件绑定的部分,后期维护可能会有点麻烦。

不过话说回来,这种小功能虽然看似简单,但真的要做得完美还挺难的。尤其是在性能和用户体验之间找到平衡点,真的很考验开发者的功底。

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

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

暂无评论