文章出處: bmfe.github.io/eros-docs/#…
前言
上篇文章 WEEX-EROS | 入門指南 中我們已經從頭執行了 eros 來進行開發 app,很多同學還不是很清楚 eros 到底能幹嘛,這裡在稍微解釋一下:
eros 是基於 weex 的二次封裝 app 開發解決方案,讓我們能用 vue 語法來開發原生 iOS/Android 應用 。
eros 已經幫助了數家公司的前端開發者開發了自己的原生 iOS/Android 應用,並通過 weex 使其 app 具有 熱釋出
的能力,在 4 月份,eros 會發布外掛系統,模擬器/真機熱重新整理,全新官方 demo ,更全面的熱更新邏輯等,正式穩定下來。
這篇文章中我們主要來介紹:
- eros 中如何使用
weex-bindingx
- 其他 weex 純 native 專案中自行封裝
weex-bindingx
來優化 JS Bundle 大小
bindingx 由來
引用官方文件上的介紹:
-
由於 weex 底層使用的 JS-Native Bridge 具有天然的非同步特性,這使得 JS 和 Native 之間的通訊會有固定的效能損耗,因此在一些複雜的互動場景中,JS 程式碼很難以高幀率執行。舉個例子,如果我們要實現
檢視隨手勢移動
的效果,那麼按照傳統的方式,需要在這個檢視上繫結touch
或者pan
事件,當手勢發生時, Native 會將手勢事件通過 Bridge 傳遞給 JS , 這產生了一次 Native 到 JS 的通訊。而 JS 在接收到事件後,需要根據手指移動的偏移量驅動介面變化,這又會產生一次 JS 到 Native 的通訊。與此同時,手勢回撥事件觸發的頻率是非常高的,頻繁的通訊帶來的時間成本很可能導致介面無法在16ms中完成繪製,進而產生卡頓。 -
事實上,不僅僅是在 weex 上存在這種問題, React Native 等框架同樣存在類似的問題。拿 React Native Animated 元件為例,為了實現流暢的動畫效果,這個元件採用了宣告式的API,在 JS 端僅僅定義了輸入與輸出以及具體的 transform 行為,而真正的動畫是通過 Native Driver 在 Native 層執行,這樣就避免了頻繁的通訊。然而,這個方案只能解決一部分問題,如果是有複雜互動操作的場景就不夠用了。另外,宣告式的方式能夠定義的行為非常有限,無法滿足更復雜的互動場景。
使用之前
請熟讀官方文件:
weex native
專案的使用過程中需要注意:
1.傳入的元素需要多取一層 ref
屬性,假設我們給一個元素上面 ref
屬性賦值為 box
,則使用時候按照以下方式才可:
this.$refs.box.ref
複製程式碼
2.如果引用方式為 requireModule(`bindingx`)
這種 weex 引入 module 的寫法,bind 方法按照文件中的方式是無效的,需要做特殊處理,後面會說到。
3.ios 端引入的時候需要做下頁面手勢返回處理 (eros 中擴充了$router gesBack
屬性),防止右滑時候出現手勢衝突
。
eros 中使用
如果下載官方的 npm 包 weex-bindingx
直接使用時沒有問題的,但 eros 是主要 focus native,引入 weex-bindingx
在打出來一個簡單的 JS Bundle 就要 190+ kb
,很明顯有些過於臃腫,在互動複雜的頁面,打出來的 JS Bundle 的大小就更加不可控。
進入 weex-bindingx
原始碼發現,如果 native 直接使用 requireModule(`bindingx`)
引入,是需要改變 expression 為物件,把填寫的表示式值傳入物件的 origin 屬性,然後傳入一個的 transformed
屬性,這個屬性是很長的 CallNative 指令字串,可以通過下載 npm 包 bindindx-parser
來自動生成。
於是 eros 做了很簡單的二次封裝,整合進 widget
中,直接通過 this
呼叫即可。
下面我們來實現一個官方的 demo:
首先編寫模板和樣式:
<template>
<div class="container" >
<div class="border">
<div :ref="`my`" class="box" @touchstart="ontouchstart">
<div class="head">
<div class="avatar"></div>
<text class="username">HACKER</text>
</div>
<div class="content">
<text class="desc">Google announced a new version of Nearby Connections for fully offline.high bandwidth peer to peer device communications.</text>
</div>
<div class="footer">
<text class="action">SHARE</text>
<text class="action" style="color:#7C4DFF">EXPLORE</text>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.container {
flex: 1;
background-color:#eeeeee;
}
.border{
height:1000px;
padding-left:35px;
padding-right:35px;
padding-top:100px;
}
.box {
width: 680px;
height: 450px;
background-color:#651FFF;
}
.head {
background-color:#651FFF;
width:680px;
height:120px;
flex-direction:row;
align-items:center;
}
.content{
width:680px;
height:240px;
background-color:#651FFF;
padding-left:24px;
padding-top:24px;
padding-right:24px;
}
.footer {
width:680px;
height:90px;
background-color: #fff;
align-items:center;
justify-content:flex-end;
padding-right:25px;
flex-direction:row
}
.action {
font-size:35;
padding-right:20px;
}
.desc {
font-size:32;
color:#fff;
padding-left:24px;
}
.avatar {
width:96px;
height:96px;
border-radius:48px;
background-color:#CDDC39;
margin-left:36px;
margin-right:48px;
}
.username {
color:#fff;
font-size:32;
}
</style>
複製程式碼
編寫 js 邏輯:
<script>
export default {
data () {
return {
x: 0,
isInAnimation: false,
opacity:1,
gesToken:0
}
},
methods: {
ontouchstart (event) {
// 如果在執行動畫,就不觸發
if(this.isInAnimation) return
// 解綁動畫
if(this.gesToken != 0) {
this.$bindingx.unbind({
eventType:`pan`,
token:this.gesToken
})
this.gesToken = 0
// return
}
// 找到元素 注意多了一個.ref
let boxRef = this.$refs.box.ref
let gesTokenObj = this.$bindingx.bind({
anchor:boxRef,
eventType:`pan`,
props: [
{element:boxRef, property:`transform.translateX`,expression:"x+0"}, // 這裡是表示式,需要有運算子號,不能只寫x
{element:boxRef, property:`opacity`,expression: "1-abs(x)/600"}] // 我們這裡期望 opacity 無論在拖動盒子向左向右的時候 都有漸隱 所有取絕對值 移動到絕對 600px 時候為全隱
}, (e) => {
if(e.state === `end`) {
// 拖動結束事件 記錄當前座標 和 透明度
this.x += e.deltaX
this.opacity = 1-Math.abs(e.deltaX)/600
// 開始進行下一步 回彈還是滑出的 動畫
this.bindTiming()
}
})
// 記錄下取消的token 多次 ontouchstart 時要帶著 token 把上次的解綁
this.gesToken = gesTokenObj.token
},
dismissCallback() {
this.$notice.toast({
message: `您已經刪除了小卡片。`
})
},
bindTiming() {
// 開始執行動畫
this.isInAnimation = true
let boxRef = this.$refs.box.ref
// 改變的 x 座標,最終的 x 座標,最終的透明值,位移 x 原點的表示式
let changed_x, final_x, final_opacity, translate_x_origin
// 通過一個變數來判斷是否已經滑出
let shouldDismiss = false
// 生成表示式邏輯
if(this.x>=-750/2 && this.x<=750/2) {
// weex 設定寬度預設都是750px 往左拖動或者往右拖動盒子一半以內時
shouldDismiss = false // 標記為不消失
final_x = 0 // 回到原點
changed_x = 0-this.x // 計算出需要位置的值
final_opacity = 1 // 透明度變回 1
translate_x_origin = `easeOutElastic(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutElastic 生成位移到原點的表示式 1s內執行
} else if(this.x < -750/2) {
// 往左拖動盒子超過一半時
shouldDismiss = true // 標記為消失
final_x = -750 // 完全把盒子位移到左邊屏外面
changed_x = -750-this.x// 計算出需要位置的值
final_opacity = 0 // 透明度變回 0
translate_x_origin = `easeOutExpo(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutExpo 生成位移到 -750px 表示式 1s內執行
} else {
// 往右拖動盒子超過一半時
shouldDismiss = true // 標記為消失
final_x = 750 // 完全把盒子位移到右邊邊屏外面
changed_x = 750-this.x // 計算出需要位置的值
final_opacity = 0 // 透明度變回 0
translate_x_origin = `easeOutExpo(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutExpo 生成位移到 750px 表示式 1s內執行
}
// 運動曲線為linear 計算出透明度表示式 1s內執行
let opacity_origin = `linear(t,${this.opacity},${final_opacity - this.opacity},1000)`
let result = this.$bindingx.bind({
eventType:`timing`, // 結束的時候是沒有任何監聽的 用 timing 來做定時的動畫
exitExpression:"t>1000", // 當時間超過 10000ms 結束動畫
props: [
{element:boxRef, property:`transform.translateX`,expression:translate_x_origin},
{element:boxRef, property:`opacity`,expression:opacity_origin}
]
},(e) => {
if(e.state === `end` || e.state === `exit`) {
// reset x
this.x = final_x
this.opacity= final_opacity
this.isInAnimation = false
shouldDismiss && this.dismissCallback()
}
})
}
}
}
</script>
複製程式碼
可以看到,只需要呼叫 $bindingx
即可。
weex native 中使用
在非 eros 的 weex native 專案中,我們可以重寫 bind
方法來保持與官方使用一致,下載 npm 包, lodash
和 bindingx-parser
來進行改造:
// bindingx.js
import { parse } from `bindingx-parser`
import _cloneDeep from `lodash/cloneDeep`
const WeexBinding = weex.requireModule(`bindingx`)
const BindingxFunction = WeexBinding.bind
let _WeexBinding = _cloneDeep(WeexBinding)
// 重寫 bind 方法
_WeexBinding.bind = (options, callback) => {
if (!options) {
throw new Error(`should pass options for binding`)
}
options.exitExpression = formatExpression(options.exitExpression)
if (options.props) {
options.props.forEach((prop) => {
prop.expression = formatExpression(prop.expression)
})
}
return BindingxFunction(options, options && options.eventType === `timing` ? fixCallback(callback) : callback)
}
module.export = _WeexBinding
複製程式碼
在業務中使用:
var bindingx = require(`bindingx`)
複製程式碼
這樣在打包之後非壓縮狀態下體積能減少到 34kb
左右。而 eros js bundle 的大小會更小,已經把這部分重寫邏輯放入了 widget
,通過 appboard.js
來內建到客戶端執行。
現在,盡情使用 bindingx 吧!