Slide滑动组件开发实战与性能优化经验分享

Des.佳鑫 组件 阅读 2,182
赞 34 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

去年年底接了个活动页项目,需求很典型:首页轮播图 + 产品卡片横向滑动 + 一个带锚点的横向导航栏。客户明确要求“必须支持手指拖拽滑动”,且要兼容 iOS 和安卓主流机型(最低 iOS 12、Android 8)。我第一反应是直接上 Swiper——毕竟用过三四次了,文档熟、API稳、社区问题一堆答案。但这次我犹豫了。

Slide滑动组件开发实战与性能优化经验分享

因为这个项目要嵌在微信公众号内嵌 WebView 里,而我们之前踩过坑:某些安卓微信版本(比如 8.0.32)的 WebView 对 Swiper 的 touchStart 拦截太狠,导致滑动卡顿、甚至触发不了 touchMove。再加上这次要同时控制 3 个不同区域的滑动行为(轮播、商品列表、导航条),Swiper 的实例管理+事件穿透一搞就是半天。最后我决定:手写一个轻量级 Slide 组件,只做最核心的事——滑动、回弹、边界限制、防抖触发回调。别的都砍掉。

最大的坑:touchmove滚动失效

开始写得很顺:监听 touchstart 记下初始 X 坐标,touchmove 算差值,touchend 判断是否超过阈值来决定是滑动还是点击。结果在真机测试时发现——只要手指一碰容器,整个页面就“锁死”了,上下滚动完全失效。不是卡,是压根不响应。

折腾了半天才发现:我忘了调 event.preventDefault(),但加了之后又出新问题——iOS 上所有 touchmove 都被阻止了,连父容器的滚动也废了。查 MDN 才意识到:preventDefault 必须在 passive: false 的监听器里才能生效,而 Vue/React 默认绑定的是 passive: true(为了性能优化)。所以光写 el.addEventListener('touchmove', handler) 是没用的,得显式传 { passive: false }

但这里还有个隐藏雷:Chrome 和 Safari 对 passive 的处理不一致。Safari 在某些版本里即使写了 { passive: false },只要你在 touchstart 里没立刻调 preventDefault,后续 touchmove 依然会被标记为 passive。最终方案是:在 touchstart 回调里,第一时间调一次 event.preventDefault()(哪怕只是占位),再把事件监听器重绑成 { passive: false } —— 这招亲测 iOS 14+、安卓 Chrome 90+ 全通。

核心代码就这几行

我把滑动逻辑抽成了一个独立 hook(Vue 3 Composition API),不依赖任何 UI 框架,CSS 只靠 transform + will-change 保帧率。下面是最关键的滑动部分:

const useSlide = (containerRef, options = {}) => {
  const { threshold = 30, onSlideEnd, onSlideStart } = options;
  let startX = 0;
  let currentX = 0;
  let isDragging = false;

  const handleTouchStart = (e) => {
    e.preventDefault(); // 关键:这里必须 prevent,否则 passive 生效后 touchmove 被禁
    startX = e.touches[0].clientX;
    currentX = 0;
    isDragging = true;
    onSlideStart?.();
  };

  const handleTouchMove = (e) => {
    if (!isDragging) return;
    e.preventDefault(); // 这里再 prevent 一次,确保 touchmove 不触发默认滚动
    const moveX = e.touches[0].clientX;
    currentX = moveX - startX;
  };

  const handleTouchEnd = (e) => {
    if (!isDragging) return;
    isDragging = false;
    if (Math.abs(currentX) > threshold) {
      onSlideEnd?.({ direction: currentX > 0 ? 'right' : 'left', distance: Math.abs(currentX) });
    }
    // 重置位移(实际项目中会用 CSS transform 动画还原)
    currentX = 0;
  };

  onMounted(() => {
    const el = containerRef.value;
    if (!el) return;
    el.addEventListener('touchstart', handleTouchStart, { passive: false });
    el.addEventListener('touchmove', handleTouchMove, { passive: false });
    el.addEventListener('touchend', handleTouchEnd, { passive: true }); // end 可以 passive
  });

  return { currentX };
};

CSS 部分更简单,就两行:

.slide-container {
  overflow-x: auto;
  scroll-behavior: smooth;
}
.slide-content {
  display: flex;
  will-change: transform;
}

注意:will-change: transform 是真·救命稻草,不加它,iOS 上快速滑动三下必掉帧。另外,overflow-x: auto 是为了保留原生滚动能力(比如用户想快速拖到最右),而不是完全接管——这点很重要,很多团队为了“统一手感”硬切到 JS 滚动,结果适得其反。

谁更灵活?谁更省事?

写完之后对比了 Swiper 和这个手写版:

  • 体积:手写版压缩后 2.3KB,Swiper 7.x 最小化也要 28KB(gzip 后 11KB)
  • 加载速度:首屏 JS 少了 100ms,实测 Lighthouse 性能分高 5 分
  • 调试成本:出了问题直接进 hook 断点,不用扒 Swiper 内部 state 机
  • 缺点也很明显:没有自动循环、没有懒加载、没有分页器、没有键盘支持(PC 端 fallback)

但我们这个项目根本不需要这些。客户说“只要手机上滑得顺就行”,我就没加。上线后埋点数据显示:98.6% 的滑动操作都在 300ms 内完成,平均滑动距离 180px,完全够用。

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

1. 别在 touchmove 里做重计算:我一开始在 handleTouchMove 里实时算 translateX 并更新 DOM,结果安卓低端机直接 15fps。改成只记 currentX,在 requestAnimationFrame 里统一更新,帧率拉回 60fps。

2. touchcancel 一定要监听:微信里用户切后台再切回来,或者弹出软键盘,都会触发 touchcancel。不处理的话,isDragging 一直为 true,后面所有滑动都失灵。补了一行 el.addEventListener('touchcancel', handleTouchEnd) 解决。

3. 别信“iOS 完全兼容”这种话:iOS 15.4 的 Safari 有个 bug,当 transform: translateX() 的值是小数(比如 12.3px)时,会强制四舍五入导致跳变。最后我做了个取整:Math.round(currentX),虽然损失了亚像素精度,但肉眼完全看不出。

回顾与反思

这个 Slide 组件上线三个月,零线上报错,也没收到用户反馈滑不动。但它确实不完美:PC 端鼠标拖拽没做(因为需求只要移动端)、没有 accessibility 支持(aria-live 没加)、也没有和 Vue Router 的 scrollBehavior 联动。不过说实话,这些功能加进去大概要多花两天,而客户验收时根本不会点开 devtools 去看 aria 标签有没有设对。

我的结论是:**对于纯活动页、生命周期短、交互路径单一的场景,手写轻量 Slide 比引入重型轮子更可控、更易维护、也更扛压。但如果你要做一个长期迭代的 CMS 后台,或者需要支持复杂手势(比如双指缩放+滑动组合),那还是老老实实用 Swiper 或者 Framer Motion 吧。**

以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多(比如结合 IntersectionObserver 做滑动懒加载),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论
司马洛熙
这个新的思路帮我优化了项目的用户流程,转化率提升了不少。
点赞 1
2026-02-16 14:25