1. 程式人生 > 實用技巧 >從零開始的微信小程式入門教程(四),理解小程式事件與冒泡機制

從零開始的微信小程式入門教程(四),理解小程式事件與冒泡機制

壹 ❀ 引

我在之前初識WXML與資料繫結兩篇文章中,介紹了小程式靜態模板與樣式相關概念,以及小程式幾種常用資料繫結方式,在知道這些知識後,我們可以寫一些不算複雜的小程式頁面,並能將一些自定義的資料渲染到檢視層,這非常棒。那麼本文我們將繼續介紹小程式中比較重要的事件概念,在學習完事件後,我們可以讓小程式具備一定的互動性,那麼本文開始。

貳 ❀ 初識小程式事件

在小程式中,事件是檢視層到邏輯層的通訊方式

比如,我們可以將事件繫結在元件上,當用戶操作該元件並觸發事件時,事件會將使用者行為反饋到邏輯層做處理,也就是對應的執行邏輯層中的事件處理函式。

當然,有時候行為反饋不一定是由使用者來主動觸發,舉個生活中的例子,我們在騰訊視訊看龍嶺迷窟時,當播放到一集結尾,視訊會自動播放下一集。將這個例子拿到小程式中來說,video元件便自帶了bindended

事件,只要視訊播放到末尾便會觸發該事件,小程式中存在很多由元件自身提供的事件,所以綜合來說,小程式中的事件由使用者行為反饋事件元件狀態反饋事件兩部分組成。

微信小程式除了WXML,WXSS檔案之外,還提供了WXS指令碼語言,為什麼突然扯到這個呢,因為從基礎庫版本2.4.4開始,支援使用WXS響應事件。針對於IOS環境,WXS指令碼執行速度是JavaScript的2-20倍,安卓環境沒啥差別,大體上來理解,用WXS解決事件問題具備一定優勢。由於我們目前暫未了解WXS指令碼,所以這裡先不做探討,後面會專門另起一篇文章介紹WXS指令碼以及事件相關說明。學習總不能一口吃成胖子,我們一步步來。

所以本文還是主要圍繞使用者行為反饋事件展開討論,讓大家對於事件先有個基本概念。

還記得上篇文章中我們接觸的第一個點選事件tap嗎?我們來重現它,並以此加深對於事件的理解、

首先,我們在index.wxml中與index.js中新增如下程式碼:

<button bindtap="alert">bindtap</button>
 1 Page({
 2   data: {},
 3   alert: function (event) {
 4     wx.showToast({
 5       title: '觸發成功', // 標題
 6       icon: 'success', // 圖示型別,預設success
 7       duration: 1500
// 提示窗停留時間,預設1500ms 8 }) 9 } 10 })

有上述例子可知,實現一個事件繫結主要分為兩步,第一步我們通過bindtap綁定了一個函式alert,第二我們在Page構造器中定義對應的事件處理函式alert。當戶點選button元件時,該元件就會在Page中找到對應的事件函式並執行,這便是一次檢視到邏輯的通訊過程。

需要注意的是,此時我們使用的事件是tapbind只是一個事件字首,這就像我們用原生JS事件時所有事件前都得加一個on,比如onclick,onchange,這是同一個道理。

除此之外,小程式中的事件支援bindtap與bind:tap兩種寫法,怎麼用都行。

1 <button bindtap="alert">bindtap</button>
2 <button bind:tap="alert">bindtap</button>

好了,在瞭解了事件基本概念後,我們來一一介紹小程式中提供的使用者互動事件。

由於大部分事件均與手指觸碰有關,所以為了大家更好的感受各個事件的作用,這裡我推薦大家開啟小程式偵錯程式的自動預覽,如下

點選編譯並預覽後,登陸的微信賬號即可預覽我們的小程式專案,如果你修改了程式碼,記得手動點一次編譯並預覽按鈕。

叄 ❀ 常見事件型別

注意,這裡的事件型別均為使用者行為反饋事件,相關說明直接引用官網,有個小規律,小程式中所有事件名均為小寫單詞拼接,無駝峰拼接情況,這點大家記住。

事件型別觸發條件
touchstart 手指觸控動作開始觸發
touchmove 手指觸控後移動觸發
touchcancel 手指觸控動作被打斷,如來電提醒,彈窗
touchend 手指觸控動作結束
tap 手指觸控後馬上離開
longpress 手指觸控後,超過350ms再離開,如果指定了事件回撥函式並觸發了這個事件,tap事件將不被觸發(與tap同時定義,優先順序更高)
longtap 手指觸控後,超過350ms再離開(推薦使用longpress事件代替)
transitionend 會在 WXSS transition 或 wx.createAnimation 動畫結束後觸發
animationstart 會在一個 WXSS animation 動畫開始時觸發
animationiteration 會在一個 WXSS animation 一次迭代結束時觸發
animationend 會在一個 WXSS animation 動畫完成時觸發

現在,我們通過例子一一加深印象,還是使用上文提供的tap事件例子,JS程式碼不變,我們只需要切換事件名即可:

1.touchstart事件

<button bindtouchstart="alert">bindtap</button>

模擬器可能還不是很明顯,大家如果通過手機預覽可以發現,由於button元件按下去有個背景變灰的漸變,而touchstart事件即是手指觸碰到元件的一瞬間方法就被執行,此時按鈕還沒完全按下去,大家多體驗幾次。

2.touchmove事件

<button bindtouchmove="alert">bindtap</button>

這個就非常明顯了,手指按下按鈕完全變灰後沒執行,一定要我們按住手指並拖動才會觸發。

3.touchcancel事件

<button bindtouchcancel="alert">bindtap</button>

這個我交大家怎麼模擬,在手機上用左手點選button元件不要放開,用右手點選小程式更多功能按鈕,此時會彈窗,由於觸碰被打斷,可以發現事件被順利觸發。

其次,左手按鈕button,點選關閉小程式,也就是這個按鈕,此時小程式會暫時退出並儲存在手機後臺中,通過後臺直接再進入小程式,我們會發現touchcancel事件也會觸發。

4.touchend事件

<button bindtouchend="alert">bindtap</button>

點選按鈕,長按拖動都不會觸發,直到手指離開螢幕便會觸發。

5.tap事件

<button bindtap="alert">bindtap</button>

上文中給了例子,雖然官方說手指觸碰後馬上離開觸發,事實證明我按住按鈕十幾秒後離開,也會觸發,我預設理解為click事件。

6.longpress事件

<button bindlongpress="alert">bindtap</button>

快速點選快速鬆開並不會觸發該事件,只有點選超過350ms時才會觸發。

7.與動畫相關的API,由於涉及到了小程式動畫,這裡先通過官方動畫例子展示API作用,動畫怎麼玩後面再做介紹(留個坑...)。

1 <view class="box {{extraClasses}}"
2   bindtransitionend="transitionEnd"
3   bindanimationstart="animationStart"
4   bindanimationiteration="animationIteration"
5 ></view>
6 
7 <button class="btn" bindtap="triggerTransition">觸發CSS漸變</button>
8 <button class="btn" bindtap="triggerAnimation">觸發CSS動畫</button>
 1 .box {
 2   width: 100rpx;
 3   height: 100rpx;
 4   margin: 60rpx;
 5   background: red;
 6 }
 7 .btn {
 8   margin: 30rpx 60rpx 0;
 9 }
10 
11 .box-transition {
12   transition: all 0.5s;
13 }
14 .box-moved {
15   margin-left: 590rpx;
16 }
17 
18 @keyframes box-ani {
19   from {margin-left: 60rpx}
20   to {margin-left: 590rpx}
21 }
22 .box-animation {
23   animation: box-ani 1s alternate infinite;
24 }
 1 const app = getApp()
 2 
 3 Page({
 4   data: {
 5     extraClasses: '',
 6   },
 7   triggerTransition: function () {
 8     if (this.data.extraClasses == 'box-transition box-moved') {
 9       this.setData({
10         extraClasses: 'box-transition'
11       })
12     } else {
13       this.setData({
14         extraClasses: 'box-transition box-moved'
15       })
16     }
17   },
18   triggerAnimation: function () {
19     this.setData({
20       extraClasses: 'box-animation'
21     })
22   },
23   transitionEnd: function () {
24     console.log('漸變已結束')
25   },
26   animationStart: function () {
27     console.log('動畫已開始')
28   },
29   animationIteration: function () {
30     console.log('動畫進行中')
31   },
32   animationend:function () {
33     console.log("動畫結束")
34   }
35 })

肆 ❀ 事件物件

在事件處理函式中,我們能接受到一個event物件引數,該引數包含了當前事件型別,以及當前元件相關資訊,具體屬性如下:

屬性型別說明
type String 當前繫結的事件型別
timeStamp Integer 頁面開啟到觸發事件所經過的毫秒數
target Object 觸發事件的元件的一些屬性值集合
currentTarget Object 當前元件的一些屬性值集合
detail Object 額外的資訊
touches Array 觸控事件,當前停留在螢幕中的觸控點資訊的陣列
changedTouches Array 觸控事件,當前變化的觸控點資訊的陣列

我們來看個例子:



<button bindtap="alert" id="btn" data-name="聽風是風" data-age="27">bindtap</button>
1 Page({
2   data: {},
3   alert: function (event) {
4     console.log(event);
5   }
6 })

通過點選按鈕任意區域,可以看到控制檯輸出如下資料:

可以看到元件上自定義的data-**資料,元件X軸Y軸偏移量,滑鼠點選時的座標等等資訊均有記錄。

伍 ❀ 複習事件冒泡與理解小程式事件捕獲/冒泡

我們知道,在JavaScript事件監聽中,分為捕獲階段--目標階段--冒泡階段三個部分。考慮到有同學對於該部分知識有遺忘,這裡做個簡單補充。

我們先來看個JavaScript的例子:

1 <div class="parent">
2 3     <div class="child">
4 5     </div>
6 </div>
 1 .parent{
 2     width: 200px;
 3     height: 200px;
 4     background-color: #bbded6;
 5 }
 6 .child{
 7     width: 100px;
 8     height: 100px;
 9     background-color: rgba(255,80,47,1);
10 }
 1 var parent = document.querySelector(".parent"),
 2     child = document.querySelector(".child");
 3 
 4 parent.addEventListener("click", function () {
 5     console.log("A");
 6 }, true);
 7 parent.addEventListener("click", function () {
 8     console.log("B");
 9 });
10 child.addEventListener("click", function () {
11     console.log("D");
12 }, true);
13 child.addEventListener("click", function () {
14     console.log("C");
15 }, false);

現在,讓我們用滑鼠點選紅色區域,猜猜會一次輸出什麼呢?

在說答案之前,我們先複習下addEventListener事件監聽引數以及含義:

element.addEventListener(event,function,useCapture);

其中event與function為必填,event表示事件型別,function為事件處理函式,而useCapture為選填,它的值為Boolean值。用於指定事件是否在捕獲或者冒泡階段執行,預設為false,即在冒泡階段執行,反之在捕獲階段執行。

而上文的例子中,我們用了一個父盒子包裹了一個子盒子,拋開根元素以及body,捕獲與冒泡如下:

捕獲階段:parent--child

冒泡階段:child--parent

再觀察上述例子關於useCapture的值,所以答案是ADCB

OK,花了點時間用於解釋JS事件監聽的冒泡概念。小程式中事件同樣存在捕獲與冒泡階段。

比如我們前面所說的bind字首就表示事件在冒泡階段執行,而如果我們想事件在捕獲階段執行,可以在bind前面加上capture,即capture-bind表示捕獲階段執行。

微信小程式中的捕獲與冒泡執行與JS事件監聽保持一致,這裡引用官方一張圖解就一目瞭然了:

 1 <view
 2   id="parent"
 3   bind:tap="tap1"
 4   capture-bind:tap="tap2"
 5 >
 6   outer view
 7   <view
 8     id="child"
 9     bind:tap="tap3"
10     capture-bind:tap="tap4"
11   >
12     inner view
13   </view>
14 </view>
1 #parent {
2   display: block;
3   background-color: #bbded6;
4   color: #fff;
5 }
6 #child{
7   background-color: rgba(255,80,47,1);
8 }
 1 Page({
 2   data: {},
 3   tap1: function (event) {
 4     console.log('tap1');
 5   },
 6   tap2: function (event) {
 7     console.log('tap2');
 8   },
 9   tap3: function (event) {
10     console.log('tap3');
11   },
12   tap4: function (event) {
13     console.log('tap4');
14   },
15 })

當我們點選inner view區域,可以看到依次輸出如下:

為了防止大家疑惑,這裡我們先做個知識小結,首先我們前面說了事件支援bind與bind:兩種寫法,這兩種寫法都不會阻止冒泡,所以如果大家分別給父子繫結bind事件,點子區域,會先執行子的bind再執行父的bind,畢竟我們沒使用capture定義捕獲階段,所以全程就只有冒泡。記住了,bind不會阻止冒泡,新增capture字首可以響應捕獲階段。

那麼問題來了,假設我們想阻止冒泡呢?這時候就得將bind替換為catch事件了,catch也支援兩種寫法catch與catch:,我們來試試下面這個例子:

 1 <view
 2   id="parent"
 3   catch:tap="tap1"
 4 >
 5   outer view
 6   <view
 7     id="child"
 8     catch:tap="tap3"
 9   >
10     inner view
11   </view>
12 </view>

現在點選inner view區域,可以發現只輸出了tap3,因為冒泡被阻止了。

那有同學又要問了,如果我在catch前新增capture字首會怎麼樣?怎麼樣咱們修改例子試試不就知道了,看下方例子:

 1 <view
 2   id="parent"
 3   bind:tap="tap1"
 4   capture-catch:tap="tap2"
 5 >
 6   outer view
 7   <view
 8     id="child"
 9     bind:tap="tap3"
10     capture-catch:tap="tap4"
11   >
12     inner view
13   </view>
14 </view>

我們將capture-bind都改為capture-catch,可以發現不管點選父區域還是子區域,都只會輸出一個tap2,這是因為capture-catch會中斷捕獲階段和取消冒泡階段。所以不管點選哪,都是從捕獲階段開始,先捕獲到父,然後中斷捕獲,也不會存在冒泡了,就這麼個意思。

OK,來個小總結。

bind不會阻止冒泡,但如果想抓捕獲階段,可以新增字首capture,也就是capture-bind

catch會阻止冒泡,如果新增capture字首,捕獲階段會中斷的同時,也會阻止冒泡。

最後的最後,官方給了個貼心說明,除了上文中我們列舉的使用者互動反饋事件之外的其它任意元件狀態反饋事件,除非有特殊宣告,否則都是非冒泡事件。也就是說,上文給的使用者互動反饋事件都是冒泡事件。

好了,關於小程式冒泡機制就聊到這。

陸 ❀ 總結

又花了一天事件整理了小程式事件相關概念,通過本文學習,我想大家對於小程式事件應該有了一個清晰的概念。小程式中的事件分為使用者互動反饋事件與元件狀態反饋事件(也就是元件自帶的事件)。但總體來說,事件就是檢視層到邏輯層的通訊方式。

除此之外,我們粗略的給出了各個事件的使用區別,幫助大家在特定的需求下能快速定位應該使用哪個事件。最後,我們複習了JS事件監聽中捕獲與冒泡的基礎概念,以此為基礎來了解了小程式中的冒泡機制。知道了原來除了bind還有catch事件,以及我們還能使用capture字首來決定是否阻止捕獲與冒泡,這對於日後開發非常有幫助。

好了,關於事件的描述就先說到這了,關於下篇文章我還沒想好寫什麼,我是通讀小程式官方文件再做的記錄,我也很怕有些知識點讀的遺漏了沒記錄清除,反正我會加油持續更新。

那麼到這裡,本文結束。