# 触摸与点击事件
用户的鼠标点击、在触屏设备上手指触摸是最常见的界面交互方式。在这些情况下,小程序基础库会提供一系列事件来描述用户的触摸与点击状态。
# 触摸与点击事件列表
所有触摸与点击事件都是冒泡事件。具体来说,它们在冒泡时都可以穿越组件边界,而且具有捕获阶段(相关概念请参考 事件系统 章节)。
# 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 接口:
# 获得点击态
# 用户点击时
每次用户点击,对应的 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 能够继承延续点击态: