小程序API实战踩坑总结与常用接口调用技巧

南宫卫红 框架 阅读 2,158
赞 43 收藏
二维码
手机扫码查看
反馈

核心代码就这几行,但得先搞懂它为啥不生效

上周上线一个小程序活动页,首页要实现「手指按住拖拽滚动卡片」的效果(类似小红书那种横向滑动卡片流)。我理所当然地写了 touchstart + touchmove + touchend,结果在真机上一滑——没反应。预览没问题,开发者工具里跑得飞起,真机上 touchmove 死活不触发。折腾了俩小时,最后发现是小程序默认阻止了 touchmove 的默认行为,而且……它还跟 scroll-view 有隐藏冲突。

小程序API实战踩坑总结与常用接口调用技巧

亲测有效的方式,不是靠猜,是靠翻文档+断点+看控制台 warn。下面我把真正能用、已上线、用户反馈「丝滑」的三套方案全贴出来,带注释、带坑点、带取舍建议。

先看效果,再看代码:原生 touch 事件手动滚动(最可控)

这是我现在主力用的方案。不依赖 scroll-view,纯 JS 计算位移,兼容性好,iOS/Android 都稳。关键点就两个:一是 catchtouchmove 要写死在容器上(别信某些教程说加 bind:touchmove 就行),二是必须 e.preventDefault()touchmove 里调,且不能被条件包裹。

<view class="card-container" catchtouchstart="onTouchStart" catchtouchmove="onTouchMove" catchtouchend="onTouchEnd">
  <view class="card-list" style="transform: translateX({{translateX}}px);">
    <view class="card" wx:for="{{cards}}" wx:key="id">{{item.title}}</view>
  </view>
</view>
Page({
  data: {
    cards: [{id: 1, title: '卡片1'}, {id: 2, title: '卡片2'}, {id: 3, title: '卡片3'}],
    translateX: 0,
    startX: 0,
    currentX: 0
  },
  onTouchStart(e) {
    this.setData({ startX: e.touches[0].clientX })
  },
  onTouchMove(e) {
    // ⚠️ 踩坑提醒:这三点一定注意
    // 1. 必须用 catchtouchmove,bind 不会阻止默认滚动行为
    // 2. 必须 e.preventDefault(),哪怕只是空函数里调一下
    // 3. 真机上 e.touches 可能为空,得判空
    if (!e.touches || e.touches.length === 0) return
    const moveX = e.touches[0].clientX
    const diff = moveX - this.data.startX
    this.setData({ currentX: this.data.translateX + diff })
  },
  onTouchEnd() {
    // 这里加个简易回弹逻辑(可选)
    const targetX = Math.abs(this.data.currentX) > 50 
      ? Math.sign(this.data.currentX) * 300 
      : 0
    this.setData({ translateX: targetX })
  }
})

这里注意下,我踩过好几次坑:一开始用 bindtouchmove,结果 iOS 上还是跟着页面一起滚;后来加了 e.preventDefault(),但写在 if 里,结果某些机型 touchmove 没进 if 就跳过了;最后发现,catchtouchmove 是唯一靠谱的入口,它天然阻止冒泡和默认行为,比手动 prevent 更稳。

这个场景最好用:scroll-view + enhanced 模式(省事但有限制)

如果你只是想横向滚动一排卡片,又不想手写 transform,scroll-view 是最快方案。但它有个硬伤:默认不支持「手指按住拖拽惯性滚动」,得开 enhanced 属性,且只在基础库 2.12.2+ 支持。

重点来了:开 enhanced 后,scroll-view 会接管 touch 事件,你自己的 catchtouchmove 就收不到事件了。所以别想着在它里面再套一层手势逻辑——没用。

<scroll-view 
  class="card-scroll" 
  scroll-x 
  enhanced 
  bindscroll="onScroll"
  style="white-space: nowrap;">
  <view class="card-inline" wx:for="{{cards}}" wx:key="id">
    {{item.title}}
  </view>
</scroll-view>

CSS 得配好:

.card-scroll {
  width: 100%;
  height: 200rpx;
  overflow: hidden;
}
.card-inline {
  display: inline-block;
  width: 300rpx;
  height: 180rpx;
  margin-right: 20rpx;
  vertical-align: top;
}

⚠️ 注意:enhanced 模式下,scroll-viewscroll-left 值在真机上有时更新延迟,别拿它做实时位置判断;另外,如果卡片内容是图片,记得加 lazy-load,不然首屏卡顿明显。

又踩坑了:wx.getSystemInfoSync().platform 返回值不可靠

有次我为了给 iOS 加个 bounce 效果,写了:

const sys = wx.getSystemInfoSync()
if (sys.platform === 'ios') {
  // 加点弹性
}

结果线上监控报错:部分 Android 机器也返回了 ios。查了一圈才发现,微信在某些定制 ROM(比如某品牌厂的深度定制系统)里,platform 字段会错报。现在我的做法是:直接用 sys.system 匹配 iPhoneiPad,更靠谱:

const sys = wx.getSystemInfoSync()
const isIOS = /iPhone|iPad/i.test(sys.system)

这个坑我踩了两次,第一次以为是缓存,清了重装还是错;第二次才去翻社区 issue,发现是系统层上报问题。所以别迷信 API 返回值,该正则还是得正则。

高级技巧:用 IntersectionObserver 做懒加载 + 触发动画

卡片流如果长,得做懒加载。但小程序的 IntersectionObserver 和 Web 版不一样——它不支持 rootMargin,只能靠监听元素是否在视口内。我现在的做法是:给每张卡片加个 data-index,在 onReady 里统一初始化 observer,进入视口时才拉真实图片或触发动画。

onReady() {
  this.observer = wx.createIntersectionObserver(this, {
    thresholds: [0.1]
  })
  this.observer.relativeToViewport({ bottom: 100 })
  this.observer.observe('.card', (res) => {
    if (res.intersectionRatio > 0) {
      const index = res.target.dataset.index
      this.loadCardImage(index) // 实际加载逻辑
      this.animateIn(index)     // 添加入场动画类
    }
  })
}

⚠️ 注意:observer 必须在 onReady 之后创建,onLoad 时 DOM 可能还没渲染完,observe 会失败;另外,别忘了在 onUnload 里调 this.observer.disconnect(),否则内存泄漏。

结语

以上是我最近三个月在三个小程序项目里反复验证过的 touch 相关 API 用法。没有银弹,scroll-view enhanced 省事但不够灵活,原生 touch 手动控最稳但得自己写边界处理。实际项目里我一般混着用:首页用原生方案保体验,列表页用 scroll-view + enhanced 保交付速度。

这个技术的拓展用法还有很多,比如结合 canvas 做手势轨迹识别、用 getMenuButtonBoundingClientRect 动态适配胶囊按钮位置做吸顶导航——后续会继续分享这类博客。

以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流。

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

暂无评论