BindingX 官網:
https://alibaba.github.io/bindingx/
BindingX 項目地址:
https://github.com/alibaba/bindingx
一. 背景
在 Weex 環境下實現一些複雜的手勢交互效果可能會產生卡頓,這是因爲每次手勢交互都會產生兩次 JS-native 通信。第一次是 native call JS,將手勢事件傳遞到 JS 層交給前端處理,當 JS 層接收到回調後,會產生第二次通信,JS call native,用來驅動界面變化。與此同時,手勢回調事件觸發的頻率是非常高的,頻繁通信帶來的時間成本很可能導致界面無法在 16ms 中完成繪製,因而產生卡頓。
我們提出了
Expression Binding
方案用來解決這個問題。方案是在手勢開始的時候,將具體的手勢控制函數以
表達式
的形式傳遞給 Native 層,當手勢發生時,Native 根據預置的表達式解析器去解釋執行表達式,並根據結果驅動視圖變化。這樣帶來的好處是大大的減少了 native-JS 的通信次數,下面兩幅圖描述了傳統方案與
Expression Binding
方案的差別:
圖 1:傳統方案
圖 2:Expression Binding 方案
事實上,
Expression Binding
不僅僅可以解決手勢交互問題,任何 JS-native 頻繁通信 + UI 更新的場景理論上都可以複用這套方案。比如:
- 監聽容器的滾動,並基於滾動距離等變量更新UI如最常見的視差動畫等;
- 監聽陀螺儀方向變化數據,並更新 UI;
- 監聽時間變化,更新 UI;
- ……
因此,我們將原方案進行了橫向的擴展,實現了這些新的特性,並將它命名爲 BindingX。2018 年 3 月,BindingX 正式開源,並同時支持了 React Native。
二. 特性一覽
1. 手勢能力
BindingX 能夠監聽元素的 pan 事件,基於此可以實現拖拽、卡片橫滑等跟手的交互效果。更令人驚喜的是,類似 Weex Slider 這樣的組件現在也可以使用 BindingX 來實現!
2. 動畫
在 Weex 上實現動畫通常的做法是使用
animation module
,現在有了新的選擇。使用 BindingX 可以實現所有 animation module 能實現的效果,另外,BindingX 內置了 30 多組常見的
插值器
,可以自由選擇,當然也可以使用 cubicBezier 貝塞爾曲線定製插值器。
3. 陀螺儀
BindingX 內置了陀螺儀監聽器,可以監聽設備方向變化。這在很多富交互場景中非常實用,比如在手機淘寶裏,你可以看到很多基於陀螺儀的視差效果:
4. 列表滾動監聽
BindingX 能夠監聽列表等滾動容器的
onScroll
事件,通過它可以實現酷炫的視差動畫:
三. 使用方式
BiningX 同時支持 ReactNative 和 Weex,對於 Weex 來說不管你是使用 Rax 還是 Vue DSL,都沒有關係。下面以 Weex 舉例來說明如何使用 BindingX。
第一步:安裝依賴
- 安裝 npm 依賴
$ npm install weex-bindingx --save
- 在 JS 代碼中引入 BindingX 模塊
import BindingX from weex-bindingx;
第二步:編寫表達式
- 根據業務場景,選擇您需要的 eventType。 比如,要監聽手勢,eventType 值爲 pan,監聽滾動容器 scrollOffset 變化,eventType 值爲 scroll。
- 根據交互行爲,選擇要改變的屬性,並編寫相應的表達式。比如,交互行爲是“用戶橫滑 100 單位,透明度從 1 變化到 0”。則屬性爲 “opacity”,表達式爲 “1 - x / 100”。
第三步:綁定表達式
根據第二步得到的 eventType、Expression 以及 Property,調用 BindingX 模塊的 bind 方法,完成綁定。
let result = BindingX.bind({
eventType: 'pan', ==> 事件類型
anchor: 'foo', ==> anchor 指的是事件的觸發者,如果是 eventType 是 “orientation” 或 “timing”,則不用填
props: [
{
element: view.ref, ==> 要改變的視圖的引用或者 id
expression: "1 - x / 100", ==> 表達式
property: "opacity" ==> 要改變的屬性
}
]
})
當調用 bind 方法之後,native 會啓動監聽,當目標事件(比如手指滑動、設備方向變化等)發生的時候,便會執行您先前綁定的一組或者多組表達式。 bind 方法會返回一個 JS 對象,其中包含了一個 token 屬性,可以使用這個 token 取消綁定。
更多細節,請參考我們的 文檔 。
第四步:取消綁定
在合適的時機調用 BindingX 的 unbind 方法取消綁定。比如,頁面不可見或者即將銷燬的時候。
BindingX.unbind({
token: result.token,
eventType: 'pan'
})
四. 內部細節
下面以 Android 爲例從 native 的視角介紹下 BindingX 的具體實現,首先我們來梳理整個流程:
-
前端通過聲明的方式定義具體的視圖變化,每個視圖變化過程都用一個三元組描述:
- element:目標元素。
- property:要改變的屬性。
- expression:表達式。通過工具生成抽象語法樹。
-
native 根據 eventType 註冊對應的事件監聽器,並將映射關係保存起來;
- 當指定的事件發生的時候,native 自行消費先前綁定的所有表達式,計算結果,並根據結果對視圖進行更新。
這個過程可以用下面這張圖描述:
在這個模型裏,輸入可以是手勢事件、滾動事件、陀螺儀方向變化事件,而輸出則是經過視圖變換的view,視圖變換的過程在 native 完成。而視圖變換的規則是通過
表達式
來描述的,一個表達式在前端聲明之後,會先通過
parser
轉成
Abstract syntax tree
,native 會通過預置的解析器來解析表達式樹,並計算出結果,根據結果去驅動視圖變化。
五. 更多想象力
事實上,
BindingX
比我們想象的更加強大,在上面那張架構圖中,輸出部分畫的是
transformed view
,但是事實上除了 view,我們還在探索更多有趣的玩法,比如:
-
BindingX 和 Lottie 結合。用 BindingX 驅動
lottie
實現動畫; -
BindingX 和
Weex SVG
結合,實現好玩的軌跡動畫、路徑跟隨動畫,甚至是 morph 變形動畫; -
BindingX 和
Shader
結合,用 BindingX 來控制着色器! - ……
六. 下一步?
BindingX 在內部經過很長時間的孵化,在上層衍生出了很多通用的業務組件,它們涵蓋了大部分的交互場景,諸如下拉刷新、轉場、聯動、視差動畫,
tab-panel
、
parallax
就是很好的例子。一個基於
BindingX
的前端交互體系正在成型,下一步我們會將它們逐漸開源到社區,敬請期待!