# 触摸与点击事件

用户的鼠标点击、在触屏设备上手指触摸是最常见的界面交互方式。在这些情况下,小程序基础库会提供一系列事件来描述用户的触摸与点击状态。

# 触摸与点击事件列表

所有触摸与点击事件都是冒泡事件。具体来说,它们在冒泡时都可以穿越组件边界,而且具有捕获阶段(相关概念请参考 事件系统 章节)。

# Touch 类事件

Touch 类事件描述单个手指在屏幕上的触摸状态。

类型 触发条件 最低版本
touchstart 手指触摸动作开始
touchmove 手指触摸后移动
touchcancel 手指触摸动作被打断,如来电提醒,弹窗
touchend 手指触摸动作结束
touchforcechange 在支持 3D Touch 的设备上,重按时会触发 1.9.90

Touch 类事件在 event 对象上有 touches 和 changedTouches 两个数组字段,分别表示当前处于触摸状态的手指触摸信息、发生变化了的手指触摸信息。

每个触摸信息对象,主要包含有以下几个字段。

字段名 含义
identifier 手指标识符,用来表示它对应于哪个手指
clientX 触摸位置的水平坐标(相对于视口)
clientY 触摸位置的竖直坐标(相对于视口)
pageX 触摸位置的水平坐标(相对于页面,计入页面滚动位置)
pageY 触摸位置的竖直坐标(相对于页面,计入页面滚动位置)

# Tap 类事件

Tap 类事件用来表达一次“点击“。它可以是触摸引发的,也可以是鼠标点击引发的。

类型 触发条件 最低版本
tap 手指触摸后马上离开、鼠标单击
longpress 手指触摸(或鼠标长按)超过 350ms 再离开,如果指定了事件回调函数并触发了这个事件,tap 事件将不被触发 1.5.0
longtap 手指触摸(或鼠标长按)超过 350ms 再离开(推荐使用longpress事件代替)

Tap 类事件的 detail 包含以下几个字段。

字段名 含义
x 水平坐标(相对于视口)
y 竖直坐标(相对于视口)

# Pointer 事件

在 PC 上,pointer 事件也会被触发,可以使用 pointer 事件来实现划入、右键等定制能力(类似于 web 标准 的 pointer 事件)。

示例:

<view
  bind:pointerdown="pointerdown"
  bind:pointerup="pointerup"
  bind:pointermove="pointermove"
>
  测试代码
</view>

右键响应的示例:在开发者工具中预览效果

# 点击态

为了防止敏感接口被滥用,部分 API 在触发时会校验「当前调用是否来自用户的点击操作」。这个校验依赖的就是点击态——只有当调用发生在用户点击后的有效范围内,接口才会正常执行。

如果这类接口在没有点击态时被调用,会报错:

can only be invoked by user TAP gesture

需要点击态的 API 接口:

API 说明
wx.openSetting
wx.requestSubscribeDeviceMessage
wx.exitMiniProgram
wx.requestSubscribeMessage 也可以在 wx.requestPayment / wx.requestOrderPayment 的回调中调用
wx.addPhoneCalendar
wx.addPhoneRepeatCalendar
wx.shareFileMessage
wx.shareVideoMessage
wx.addFileToFavorites
wx.addVideoToFavorites
wx.getUserProfile
wx.openCustomerServiceChat
wx.chooseLicensePlate
wx.openSystemBluetoothSetting
wx.openAppAuthorizeSetting
wx.openOfficialAccountArticle
wx.notifyGroupMembers
wx.shareFileToGroup
wx.shareImageToGroup
wx.shareVideoToGroup
wx.openOfficialAccountProfile
wx.shareToOfficialAccount
wx.openChannelsLiveNoticeInfo
wx.openInquiriesTopic
wx.openDesignerProfile
wx.navigateToMiniProgram 只要用户点击过页面任意位置(且中途未离开当前小程序)即可调用,不要求调用发生在 tap 事件回调内
wx.openEmbeddedMiniProgram 无点击态时不会报错,而是降级为 wx.navigateToMiniProgram 打开全屏小程序;但若用户从未点击过页面任意位置,仍会报错
wx.navigateTo、wx.redirectTo、wx.reLaunch、wx.switchTab、wx.navigateBack 仅在小程序以聊天工具子包模式运行时要求点击态
VideoContext.startCasting、VideoContext.switchCasting、VideoContext.reconnectCasting、VideoContext.exitCasting 无点击态时接口被静默跳过:不报错,也不会触发 success / fail / complete 回调
LivePlayerContext.startCasting、LivePlayerContext.switchCasting、LivePlayerContext.reconnectCasting、LivePlayerContext.exitCasting 无点击态时接口被静默跳过:不报错,也不会触发 success / fail / complete 回调

# 获得点击态

# 用户点击时

每次用户点击,对应的 tap 事件回调函数就会获得点击态。

# 特定 API 的回调内

以下 API 的回调内,由于 API 的回调函数是用户点击时触发的,因此具有点击态。

API 说明
wx.requestPayment / wx.requestOrderPayment 获得的点击态不能用于触发 wx.openEmbeddedMiniProgram,详见支付后打开半屏小程序能力的相关调整通知
wx.requestAutomaticRepayment
wx.showModal / wx.showActionSheet 点击态仅在 success 回调、或接口调用成功后的 complete 回调中获取

# 点击态的有效期

点击态只在当前事件回调所在的宏任务内有效,核心规则是:

  • ✅ 同步代码,以及仍处在同一个宏任务内的微任务有效。

    // <button bind:tap="handleTap">tap</button>
    handleTap: async () => {
      wx.openSetting({}); // ✅ 有效(同步调用,就在 tap 事件回调的宏任务内)
    
      Promise.resolve().then(() => {
        wx.openSetting({}); // ✅ 有效(微任务,仍未跳出当前宏任务)
      });
    
      await Promise.resolve(); // await 一个已 resolve 的值,不产生新宏任务
      wx.openSetting({}); // ✅ 有效(仍然在同一个宏任务内)
    };
    
  • ❌ 一旦跨入新的宏任务,点击态即失效。典型场景:setTimeout / setInterval 的回调,以及 await 一个真正的异步任务 await 结束后继续执行的代码已经运行在另一个宏任务里,此时不再拥有点击态。

    // <button bind:tap="handleTap">tap</button>
    handleTap: async () => {
        wx.openSetting({}); // ✅ 有效(同步调用,就在 tap 事件回调的宏任务内)
        setTimeout(() => {
            wx.openSetting({}); // ❌ 报错:can only be invoked by user TAP gesture
        }, 0);
        await new Promise((resolve) => setTimeout(resolve, 1000)); // 等待期间跨入新宏任务
    
        wx.openSetting({}); // ❌ 报错:can only be invoked by user TAP gesture
    };
    

# 点击态在异步请求中的延续

在 tap 事件处理函数里发起异步网络请求是常见场景。因此网络请求等接口在拥有点击态时被调用,会把点击态延续到自身的回调:

// <button bind:tap="handleTap">tap</button>
handleTap: async () => {
  // ✅ 点击态有效
  wx.request({
    url: "https://www.thissitedoesnotexist.com/step1",
    complete: () => {
      // ✅ 点击态依然有效,被 request 延续
      wx.request({
        url: "https://www.thissitedoesnotexist.com/step2",
        complete: () => {
          // ✅ 点击态依然有效,被 request 延续
        },
      });
    },
  });
};

以下 API 能够继承延续点击态: