# 事件

事件将用户在渲染层的操作，反馈到逻辑层，触发对应的事件处理函数，进而响应用户的交互。事件对象可以传递额外的数据，如 id, dataset, touches。

## 事件使用示例

- 在组件中绑定一个事件处理函数，如`bindtap="handleTap"`

```html
<view id="testView" data-hi="QuickApp" bindtap="handleTap">点击</view>
```

- 在相应的 Page 定义中编写事件处理函数，参数是事件对象 event。

```js
Page({
  handleTap: function(event) {
    console.log(event)
  }
})
```

- 打印的 event 结构如下：

```json
{
  "type": "tap",
  "timeStamp": 868,
  "target": {
    "id": "testView",
    "dataset": {
      "hi": "QuickApp"
    }
  },
  "currentTarget": {
    "id": "testView",
    "dataset": {
      "hi": "QuickApp"
    }
  },
  "detail": {
    "x": 59,
    "y": 36
  },
  "touches": [
    {
      "identifier": 0,
      "pageX": 59,
      "pageY": 36,
      "clientX": 59,
      "clientY": 36
    }
  ],
  "changedTouches": [
    {
      "identifier": 0,
      "pageX": 59,
      "pageY": 36,
      "clientX": 59,
      "clientY": 36
    }
  ]
}
```

## 使用 QJS 函数响应事件

使用 QJS 函数处理事件这种情况下，QJS 函数接受 2 个参数，第一个是 event 对象，event 除了有上面基本的属性外，还具有一个 `instance` 属性；第二个参数是 `ownerInstance`，和`event.instance` 一样是一个 `ComponentDescriptor` 对象。

代码示例：

- 在 qxml 中绑定 QJS 函数。

```html
<qjs module="qjs" src="./test.qjs"></qjs>
<view id="testView" data-hi="QuickApp" bindtap="{{qjs.handleTap}}"> Click Button </view>
```

**注意：绑定的 QJS 函数必须用{{}}括起来**

- test.qjs 文件实现 handleTap 函数

```js
function handleTap(event, ownerInstance) {
  console.log('tap button', JSON.stringify(event))
}
module.exports = {
  handleTap: handleTap
}
```

`ComponentDescriptor` 对象拥有一些方法，可以设置组件的样式等。 [更多详情请参考文档](./interactive-animation.html)。

## 事件详解

### 事件分类

快应用渲染层中事件分为冒泡事件和非冒泡事件：

1. 冒泡事件是指当一个组件上的事件被触发后，该事件会向父节点传递。
2. 非冒泡事件是指当一个组件上的事件被触发后，该事件不会向父节点传递。

冒泡事件列表：

| 类型               | 触发条件                                                                                  |
| ------------------ | ----------------------------------------------------------------------------------------- |
| touchstart         | 手指触摸动作开始                                                                          |  |
| touchmove          | 手指触摸后移动                                                                            |  |
| touchcancel        | 手指触摸动作被打断，如来电提醒，弹窗                                                      |  |
| touchend           | 手指触摸动作结束                                                                          |  |
| tap                | 手指触摸后马上离开                                                                        |  |
| longpress          | 手指触摸后，超过 350ms 再离开，如果指定了事件回调函数并触发了这个事件，tap 事件将不被触发 |  |
| longtap            | 手指触摸后，超过 350ms 再离开（推荐使用 longpress 事件代替）                              |  |
| transitionend      | 会在 transition 或 qa.createAnimation 动画结束后触发                                      |  |
| animationstart     | 会在一个 animation 动画开始时触发                                                         |  |
| animationiteration | 会在一个 animation 一次迭代结束时触发                                                     |  |
| animationend       | 会在一个 animation 动画完成时触发                                                         |  |

**注：其他事件如无特殊声明都是非冒泡事件，如 [form](../../component/form.html) 的`submit`事件，[input](../../component/input.html) 的`input`事件，[scroll-view](../../component/scroll-view.html) 的`scroll`事件**

### 事件绑定和冒泡

事件绑定的写法和组件的属性相同。

- key 以`bind`或`catch`开头，后面拼接事件的类型，如`bindtap`、`catchtouchstart`。`bind`和`catch`后也可以紧跟一个冒号，如`bind:tap`、`catch:touchstart`，作用一致。
- value 是一个字符串，需要在对应的 Page 中定义同名的函数。否则当触发事件的时候会报错。
- bind 与 catch 的区别：bind 事件绑定不会阻止冒泡事件向上冒泡，而 catch 则会。

代码示例：

```html
<view id="view1" bindtap="handleTap1">
  view1
  <view id="view2" catchtap="handleTap2">
    view2
    <view id="view3" bindtap="handleTap3">
      view3
    </view>
  </view>
</view>
```

执行效果：

- 点击 view3 会先后调用`handleTap3`和`handleTap2`(这是因为 tap 事件会冒泡到 view2，而 view2 阻止了 tap 事件冒泡，不再向父节点 view1 传递)
- 点击 view2 只触发`handleTap2`
- 点击 view1 只触发`handleTap1`

### 事件的捕获阶段

捕获阶段位于冒泡阶段之前，且在捕获阶段中，事件到达节点的顺序与冒泡阶段相反。捕获阶段监听事件，可以采用`capture-bind`、`capture-catch`关键字，后者将中断捕获阶段和取消冒泡阶段。

示例代码：

```html
<view id="view1" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  view1
  <view id="view3" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    view3
  </view>
</view>
```

执行效果：

- 点击 view3 会先后调用`handleTap2`、`handleTap4`、`handleTap3`、`handleTap1`

示例代码：(上面代码中的第一个`capture-bind`改为`capture-catch`)

```html
<view id="view1" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  view1
  <view id="view3" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    view3
  </view>
</view>
```

执行效果：

- 点击 view3 只触发`handleTap2`

### 事件对象

当组件触发事件时，逻辑层绑定的处理函数会收到一个事件对象。

| 属性                              | 类型    | 说明                                         |
| --------------------------------- | ------- | -------------------------------------------- |
| type                              | String  | 事件类型                                     |  |
| timeStamp                         | Integer | 事件生成时的时间戳                           |  |
| [target](#target)                 | Object  | 触发事件的组件的属性值集合                   |  |
| [currentTarget](#currenttarget)   | Object  | 当前组件的属性值集合                         |  |
| [detail](#detail)                 | Object  | 自定义事件对象额外的信息                     |
| [touches](#touches)               | Array   | 触摸事件，当前停留在屏幕中的触摸点信息的数组 |
| [changedTouches](#changedTouches) | Array   | 触摸事件，当前变化的触摸点信息的数组         |

各属性详细信息如下。

### target

触发事件的源组件。

| 属性                | 类型   | 说明                                                  |
| ------------------- | ------ | ----------------------------------------------------- |
| id                  | String | 触发事件的组件 id                                     |
| [dataset](#dataset) | Object | 触发事件的组件上由 `data-` 开头的自定义属性组成的集合 |

### currentTarget

事件绑定的当前组件。

| 属性                | 类型   | 说明                                          |
| ------------------- | ------ | --------------------------------------------- |
| id                  | String | 当前组件的 id                                 |
| [dataset](#dataset) | Object | 当前组件上由`data-`开头的自定义属性组成的集合 |

**说明： target 和 currentTarget 可以参考上例中，点击 view3 时，`handleTap3` 收到的事件对象 target 和 currentTarget 都是 view3，而 `handleTap2` 收到的事件对象 target 就是 view3，currentTarget 就是 view2。**

### dataset

在组件节点中可以定义一些自定义数据，触发事件时会传入回调中处理。

在 QXML 中，这些自定义数据以 `data-` 开头，多个单词由连字符 `-` 连接。这种写法中，连字符写法会转换成驼峰写法，而大写字符会自动转成小写字符。如：

- `data-custom-value` ，最终会呈现为 `event.currentTarget.dataset.customValue` ；
- `data-customValue` ，最终会呈现为 `event.currentTarget.dataset.customvalue` 。

### touches

touches 是一个数组，每个元素为一个 Touch 对象。 表示当前停留在屏幕上的触摸点。另外 canvas 触摸事件中携带的 touches 是 CanvasTouch 数组。

#### Touch 对象

| 属性             | 类型   | 说明                                                                     |
| ---------------- | ------ | ------------------------------------------------------------------------ |
| identifier       | Number | 触摸点的标识符                                                           |
| pageX, pageY     | Number | 距离文档左上角的距离，文档的左上角为原点 ，横向为 X 轴，纵向为 Y 轴      |
| clientX, clientY | Number | 距离页面可显示区域（屏幕除去导航条）左上角距离，横向为 X 轴，纵向为 Y 轴 |

#### CanvasTouch 对象

| 属性       | 类型   | 说明                                                                       |
| ---------- | ------ | -------------------------------------------------------------------------- |
| identifier | Number | 触摸点的标识符                                                             |  |
| x, y       | Number | 距离 Canvas 左上角的距离，Canvas 的左上角为原点 ，横向为 X 轴，纵向为 Y 轴 |  |

### changedTouches

changedTouches 数据格式同 touches。 表示有变化的触摸点，如从无变有（touchstart），位置变化（touchmove），从有变无（touchend、touchcancel）。

### detail

自定义事件数据，详见[组件](../../component)定义中各个事件的定义。

点击事件的`detail` 带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。
