在nvue中使用BindingX实现高性能滚动条
场景:nvue中的list组件没有滚动条,自己写了一个,实现方法:通过监听容器的滚动然后使用绝对定位改变滚动条的y值,但是列表在加载时的一些操作会阻塞线程,导致滚动条不能及时滚动到相应位置。
实现代码:
template:
<view class="scrollBar" :style="{height:`${appHeight}px`}">
<view class="bar" :style="{top:`${scrollBarY}px`></view>
</view>script 监听滚动事件片段:
// listHeight为列表的总高度,appHeight为显示区域的高度
handleScroll(e){
let y = Math.abs(e.contentOffset.y);
this.scrollBarY = y / this.listHeight * this.appHeight;
}第一个想到的解决方法是使用Worker,把加载的操作放到其他线程去执行,然后去看乐一下文档
App:App的js是在独立的jscore运行的,如果需要在另一个线程运行js,可以使用web-view组件或renderjs,这样的js运行在webview里,和jscore里的js是两个线程。但注意多个webview之间的js是一个进程,使用webview里的js时注意会影响视图层的渲染。
虽然App里不能使用Worker,但得到一个非常有用的信息,主线程和视图层是不同的线程,那么问题就好解决了,让改变视图的操作在视图层运行就好了。
关于WXS的介绍:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxs/
看了一下renderjs,他与WXS类似,都能降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力。
官方的描述:renderjs是一个运行在视图层的js。它比WXS更加强大。它只支持app-vue和h5。
不支持nvue,然后去找WXS的文档,也不支持nvue,但是得到了其他的解决方案:App端nvue解决此类需求,不应该使用wxs,而是使用bindingx。
uni-app 是逻辑层和视图层分离的。此时会产生两层通信成本。比如拖动视图层的元素,如果在逻辑层不停接收事件,因为通信损耗会产生不顺滑的体验。BindingX 是weex提供的一种预描述交互语法。由原生解析BindingX规则,按此规则处理视图层的交互和动效。不再实时去js逻辑层运行和通信。BindingX类似一种强化版的css,运行性能高,但没有js那样足够强的编程灵活性。uni-app 内置了 BindingX,可在 nvue 中使用 BindingX 完成复杂的动画效果。
然后找到BindingX官方文档:https://alibaba.github.io/bindingx/guide/cn_introduce
BindingX 的核心思想就是将"交互行为"以表达式的方式描述,并提前预置到Native,避免在行为触发时JS与native的频繁通信。
一句话概括使用方法:给指定DOM绑定需要监听的事件(pan/timing/scroll/orientation),通过表达式改变指定DOM的样式。
uniapp官方文档中的案例:https://uniapp.dcloud.io/nvue-api?id=nvue-%e9%87%8c%e4%bd%bf%e7%94%a8-bindingx
最后结合在BindingX文档中的内容,改变场景中滚动条的实现方式,完整代码:
<template>
<view class="wrapper">
<view class="scrollBar" :style="{height:`${barWrapHeight}px`}">
<view id="bar" class="bar" :style="{height:`${barHeight}px`}" ref="scrollBar" @touchmove="barMove" @touchstart="barClick" ></view>
</view>
<list id="list" class="list" ref="list" :style="{height:`${appHeight}px`}" :show-scrollbar="false">
<cell class="item" v-for="i in len" :key="i" :ref="`item${i}`">
<text>{{ i }}</text>
</cell>
</list>
</view>
</template>
<script>
const Binding = uni.requireNativePlugin('bindingx')
const dom = uni.requireNativePlugin('dom')
export default {
data() {
return {
len:100, // 列表的长度
barHeight:60, // 滚动条的高度
barWrapHeight:0, // 滚动条容器的高度
listHeight:0, // 列表的高度 包括可滚动区域
scrollBarD:0,
}
},
mounted() {
this.barWrapHeight = this.appHeight;
this.listHeight = 100 * this.len;
this.$nextTick(()=>{
let barWrapHeight = this.barWrapHeight - this.barHeight,
scrollHeight = this.listHeight - this.appHeight;
let bar = this.getEl(this.$refs.scrollBar)
this.binding()
})
},
methods: {
binding(){
let barWrapHeight = this.barWrapHeight - this.barHeight,
scrollHeight = this.listHeight - this.appHeight;
let bar = this.getEl(this.$refs.scrollBar)
// 监听容器的滚动
this.scrollToken = Binding.bind({
anchor: this.getEl(this.$refs.list),
eventType:'scroll',
props:[
{
element: bar,
property: 'transform.translateY',
expression: `y / ${scrollHeight} * (${barWrapHeight})`
}
]
})
},
getEl(el) {
if (typeof el === 'string' || typeof el === 'number') return el;
if (WXEnvironment) {
return el.ref;
} else {
return el instanceof HTMLElement ? el : el.$el;
}
},
barClick(e) {
let query = uni.createSelectorQuery().in(this);
query.select('#bar').boundingClientRect(data=>{
this.scrollBarD = e.touches[0].screenY - data.top;
}).exec()
},
barMove(e) {
let top = e.touches[0].screenY - this.scrollBarD; // 鼠标移动到的位置
if (top >= 0 && top <= this.barWrapHeight) {
let index = parseInt(top / this.barWrapHeight * this.len);
this.scrollList(index)
}
},
scrollList(index){
if(this.wait) return;
else{
this.wait = true;
setTimeout(()=>{
this.wait = false;
},100)
dom.scrollToElement(this.$refs[`item${index}`][0],{})
}
}
},
computed:{
appHeight(){
return this.$store.state.appHeight;
}
}
}
</script>
<style>
.item{
color: white;
height: 100px;
background-color: #007AFF;
align-items: center;
justify-content: center;
}
.scrollBar {
position: fixed;
right: 0;
top: 0;
z-index: 10;
width: 20px;
}
.bar {
position: absolute;
background-color: #232323;
width: 15px;
right: 3px;
border-radius: 20px;
}
</style>