问题
weex为前端赋予了强大的跨端开发能力和较一致的良好的用户体验。weex一般是与native进行协作开发:
- 同app内不同页面用两者分别开发,统一串联
- 同页面两者协作开发,比如native提供组件,weex使用
由于native布局一般采用定高不定宽的方式,以求不同尺寸的屏幕可以得到基本一致的布局体验,然而weex布局采用基于750的等比例缩放,这种不一致导致的问题:
- 同一元素在不同屏幕的尺寸展现不一致,大屏手机尺寸偏大,小屏手机尺寸偏小
- 与native尺寸偏差较多
字号的差异也比较明显。
默认依据ip6尺寸布局,惨不忍睹的现场直击...
- 红框部分的滑动组件是native的,而背景容器是weex布局的,由于weex等比例缩放,而native使用pt,导致iphone 5s native组件溢出,而iphone 6p组件相比于容器过小。
- 下面是iphone 6p下native列表和weex列表字号对比
思考及分析
weex目前的尺寸处理方式:
无视屏幕分辨率和尺寸,虚拟宽度统一为750,布局尺寸以750为基准按比例缩放。
那么使用weex原生尺寸定义想实现native的体验“在不同尺寸手机上同样元素的尺寸基本一致”是不太可能的。
不过weex在Js运行时提供了屏幕的物理分辨率和像素比例,那么运行时可不可以用js动态算一下呢?
我们定义weex的原生尺寸为vp(virtual pixel),视觉稿的尺寸或native的尺寸为ns(native size)。
期待的转换规则为
vp = translateRule(dp)
这样用户只需要按native的方式写布局尺寸,就可以映射为weex的尺寸,被weex布局逻辑动态解析,从而达到我们的目的。
既然我们要模拟Native的尺寸适配,首先要知己知彼,简单了解了一下native的适配方法
安卓尺寸适配
安卓提供了一个万能的适配单位:dp,dp屏蔽了屏幕大小和分辨率,提供了一致体验,dp和物理像素的换算关系如下:
dp = px / (dpi / 160)
欲转换先取dpi,可惜weex没有dpi,但提供了一个比例scale,可用来计算dpi
scale = dpi / 160
换算可得
vp = dp * 750 / ( deviceWidth / scale )
IOS尺寸适配
类似dp的单位是着色器(似乎等同于pt?)
pt = px / scale
换算可得
vp = pt * 750 / ( deviceWidth / scale )
通过分析我们发现,安卓和IOS的适配规则殊途同归,可统一转换规则为:
function translateRule(dp) {
return dp * 750 / ( deviceWidth / scale );
}
到这里似乎大功告成,只要在写样式时祭出我们的法宝:计算属性,并动态赋到模板节点的style属性上即可。然而这种方式让开发同学想起了前端茹毛饮血的上古时代,而今前端显然已经沐浴在现代化的春风里很久了。如果在写样式的时候需要写固定尺寸时,能像native一样直接写pt/dp这样的单位那肯定是极好的。
扫了一下经过weex-loader编译的代码,与vue-loader编译的区别主要是在style的处理上,前者将style编译为map形式,并挂载到Vue实例上,所以我们在两个位置可以拿到编译后的style map
- vue实例生命周期内
- vue导出的组件对象上
由此有两个插入自动处理尺寸逻辑的方案:
- 运行时。于上述2的位置,扫描vue实例的style map,过滤出自定义单位和值并替换为weex识别的尺寸
- 编译时。通过webpack loader对vue文件进行预处理,扫描自定义单位并于1位置注入运行时处理尺寸的逻辑
分析
方案一:
- 性能较差,需要遍历每个页面整个组件树的style map
- vue实例拿到的style map,已被weex-loader过滤掉自定义单位,变成了人畜无害的数字。。。
- 需要js代码处理,一看就不是weex的亲儿子单位
结论:放弃
方案二:
- 每个组件单独注入修改尺寸的代码,免去了遍历组件树的开销
- 在weex-loader的样式分析之前解析,可以拿到原始样式代码
- 纯编译时纯工具处理,使用自定义单位如同使用原生单位一样方便,weex开发者毫无感知
结论:靠谱
实现
主流程
- 识别style里的自定义单位/函数,抽取单位前的数值
- 根据规则生成尺寸处理逻辑的代码
- 注入到vue实例的beforeCreate函数里
样式识别支持less和css
单位一般用于单参数映射规则,多参数映射规则可写成函数
规则文件
基于以下原因,需要独立的、用户自定义的、vue实例里可以方便引用的规则文件
- 可能需要多个单位,因此需要一个规则集合
- 规则需要支持自定义,方便使用者修改和扩展
- 用户可能也需要手动调用某个单位的计算规则,比如实现动画
loader实现内部提供了一个默认的规则文件,支持用户自定义规则文件去覆盖和扩展默认规则
默认规则
单位/函数 | 描述 |
---|---|
dp | 模拟安卓dp单位, 兼容ios,与native布局尺寸相同 |
mq | media query |
vw | 相对于屏幕宽度的百分比 |
vh | 相对于屏幕高度的百分比 |
数据流图如下:
效果
无痛使用
.class-name {
/*
自定义单位/处理函数dp, vw
多个参数可用函数形式,单参数建议用单位
*/
width: 100dp;
height: 200vw;
font-size: dp(24, 750); //可直接用24dp,这里仅做例子
}
UI效果
- 不同尺寸屏幕native组件和weex容器完美契合(贴图不是实际屏幕大小,所以5s布局尺寸看着比较大,仅做页面内native和weex的尺寸比较)
- weex与native文字尺寸基本一致(由于weex安卓使用小数字号有问题,所以计算后的字号做了四舍五入取整,且字体也差异,所以效果还是稍微有点差异,但对比做适配前已经好很多了)
扩展
因为实现原理是编译时解析自定义样式函数,并插入运行时js代码。因此除了用于计算尺寸外,还可以计算其他依赖环境(比如屏幕尺寸、设备类型等)的样式,成为一个通用的weex css运行时处理工具。
当然更期待着weex原生支持各种布局单位,这才是康庄大道。
包地址:http://web.npm.alibaba-inc.com/package/@ali/weex-css-runtime-loader