文章出處: bmfe.github.io/eros-docs/#…前端
上篇文章 WEEX-EROS | 入門指南 中咱們已經從頭運行了 eros 來進行開發 app,不少同窗還不是很清楚 eros 到底能幹嗎,這裏在稍微解釋一下:vue
eros 是基於 weex 的二次封裝 app 開發解決方案,讓咱們能用 vue 語法來開發原生 iOS/Android 應用 。ios
eros 已經幫助了數家公司的前端開發者開發了本身的原生 iOS/Android 應用,並經過 weex 使其 app 具備 熱發佈
的能力,在 4 月份,eros 會發布插件系統,模擬器/真機熱刷新,全新官方 demo ,更全面的熱更新邏輯等,正式穩定下來。git
這篇文章中咱們主要來介紹:github
weex-bindingx
weex-bindingx
來優化 JS Bundle 大小引用官方文檔上的介紹:express
因爲 weex 底層使用的 JS-Native Bridge 具備自然的異步特性,這使得 JS 和 Native 之間的通訊會有固定的性能損耗,所以在一些複雜的交互場景中,JS 代碼很難以高幀率運行。舉個例子,若是咱們要實現 視圖隨手勢移動
的效果,那麼按照傳統的方式,須要在這個視圖上綁定 touch
或者 pan
事件,當手勢發生時, Native 會將手勢事件經過 Bridge 傳遞給 JS , 這產生了一次 Native 到 JS 的通訊。而 JS 在接收到事件後,須要根據手指移動的偏移量驅動界面變化,這又會產生一次 JS 到 Native 的通訊。與此同時,手勢回調事件觸發的頻率是很是高的,頻繁的通訊帶來的時間成本極可能致使界面沒法在16ms中完成繪製,進而產生卡頓。npm
事實上,不只僅是在 weex 上存在這種問題, React Native 等框架一樣存在相似的問題。拿 React Native Animated 組件爲例,爲了實現流暢的動畫效果,這個組件採用了聲明式的API,在 JS 端僅僅定義了輸入與輸出以及具體的 transform 行爲,而真正的動畫是經過 Native Driver 在 Native 層執行,這樣就避免了頻繁的通訊。然而,這個方案只能解決一部分問題,若是是有複雜交互操做的場景就不夠用了。另外,聲明式的方式可以定義的行爲很是有限,沒法知足更復雜的交互場景。bash
請熟讀官方文檔:weex
weex native
項目的使用過程當中須要注意:app
1.傳入的元素須要多取一層 ref
屬性,假設咱們給一個元素上面 ref
屬性賦值爲 box
,則使用時候按照如下方式纔可:
this.$refs.box.ref
複製代碼
2.若是引用方式爲 requireModule('bindingx')
這種 weex 引入 module 的寫法,bind 方法按照文檔中的方式是無效的,須要作特殊處理,後面會說到。
3.ios 端引入的時候須要作下頁面手勢返回處理 (eros 中拓展了$router gesBack
屬性),防止右滑時候出現手勢衝突
。
若是下載官方的 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
便可。
在非 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 吧!