我们都知道小程序本质上是运行在安卓端,苹果端的混合APP,只是微信提供了一套JSBridge,方便用户对一些原生功能和微信相关的功能的进行调用。而微信为了安全和性能的需要,一改以往网络架构中的单线程架构,改为小程序的双线程架构。分别是AppServie 和 Webview 两个线程,我们在小程序中编写的JS代码就是运行在AppService线程的JSCore引擎(类似V8 引擎,一个Js解释器)中,而我们的Wxml和Wxss则会依赖WebView线程进行渲染。
这样的架构虽然已经极大了提高了webview的渲染性能,但是依然会存在一些问题比如:
当页面节点数目过多,很容易发生卡顿
当我们新建一个页面,就要新建一个Webview进行渲染
页面之间共享资源,需要使用Native进行通信,就会消耗更多性能
当AppService(逻辑层)与Webview(视图层)通信也需要依赖Native
所以为了解决这些问题小程序推出Skyline渲染引擎
在Skyline环境中,Skyline 会创建了一条渲染线程来负责 Layout, Composite 和 Paint 等渲染任务,并在 AppService 中划出一个独立的上下文,来运行之前 WebView 承担的 JS 逻辑、DOM 树创建等逻辑。说白了就是之前的样式计算是放到渲染线程来处理,现在把和样式相关的逻辑也放到AppService线程中处理,个人猜测这个渲染线程很有可能很有可能就是flutter,这样的架构就极大减少内存的消耗,和线程上通信时间的消耗。原本wxs中的逻辑,也可以移到Appservice线程中运行
在app.json 文件添加 'lazyCodeLoading': 'requiredComponents'
属性,这是因为Skyline 依赖按需注入的特性。
{ 'pages': [ 'pages/index/index', 'pages/logs/logs', 'pages/test/test'
], 'window': { 'backgroundTextStyle': 'light', 'navigationBarBackgroundColor': '#fff', 'navigationBarTitleText': 'Weixin', 'navigationBarTextStyle': 'black'
}, 'sitemapLocation': 'sitemap.json', // 在 app.json 文件添加
'lazyCodeLoading': 'requiredComponents'}
在全局或页面配置中声明为 Skyline 渲染,即 app.json 或 page.json 配上'renderer': 'skyline'
Skyline 不支持页面全局滚动,需在页面配置项加上 'disableScroll': true
,在需要滚动的区域使用scroll-view 实现
Skyline 不支持原生导航栏,需在页面配置项加上 'navigationStyle': 'custom'
,并自行实现自定义导航栏
{ 'usingComponents': {}, // 在 app.json 文件添加或者page页面的json中添加 'disableScroll': true, 'navigationStyle': 'custom' //也可以放在App.json文件中 'renderer': 'skyline' }
组件适配,参考 Skyline 基础组件支持与差异
WXSS 适配,参考 Skyline WXSS 样式支持与差异
在本地设置中勾选Skyline渲染调试,如果看不到这个选项框,看一下是否在app.json中配置了'renderer': 'skyline'
小程序采用双线程架构,渲染线程(UI 线程)和逻辑线程(JS 线程)分离。JS
线程不会影响 UI
线程的动画表现,如滚动效果。但引入的问题是,UI
线程的事件发生后,需跨线程传递到 JS
线程,进而触发开发者回调,当做交互动画(如拖动元素)时,这种异步性会带来较大的延迟和不稳定,worklet
动画正是为解决这类问题而诞生的,使得小程序可以做到类原生动画般的体验
function helloWorklet() { 'worklet'; //'worklet'声明该函数为work函数,可以在js线程和UI线程中调用
console.log('hello worklet');
}
Page({
onLoad(options) {
helloWorklet('hello') // print: hello
wx.worklet.runOnUI(helloWorklet)()
},
})
在小程序控制台可以看到如下输出
function slave() { 'worklet'; return 'I am slave'}function master() { 'worklet'; const value = slave() console.log(value); }
const {runOnUI ,runOnJS} = wx.workletfunction jsFun(message) { // 普通函数不需要声明为worklet
console.log(message)
}function uiFun() { 'worklet';
runOnJS(jsFun)('I am from UI')
}
由worklet函数捕获的静态变量,会在编译期间序列化后生成在UI线程的拷贝环境之中,这就导致我们在JS线程中后续更新了变量,但是在UI线程中时得不到最新的数值的。
const obj = { name: 'skyline'}function someWorklet() { 'worklet' console.log(obj.name) // 输出的仍旧是 skyline} obj.name = 'change name'wx.worklet.runOnUI(someWorklet)()
因此shyline使用shared来实现线程之间数据的共享
const { shared, runOnUI } = wx.workletconst offset = shared(0)function someWorklet() { 'worklet'
console.log(offset.value) // 输出的是新值 1}
offset.value = 1runOnUI(someWorklet)()
注意:编辑器版本:1.06.2303162基础库版本:2.30.2
先看效果
代码如下 <br>
wxml 代码
<navigation-bar title='探探' /><view class='page'> <block wx:for='{{containers}}' wx:key='*this'> <pan-gesture-handler data-id='container-{{index}}' onGestureEvent='handlePan'> <view id='container-{{index}}' class='container' style='z-index: {{zIdnexes[index]}};background-image: url({{partContentList[index]}});'> </view> </pan-gesture-handler> </block></view>
scss代码
.page{ display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
position: relative;
.container{ height: 80vh;
width: 95vw;
background-color: burlywood;
position: absolute;
border-radius: 16rpx;
display: flex;
justify-content: center;
align-items: center;
background-size: cover;
.image{ display: block;
height: 1067rpx;
width: 712rpx;
margin: 0 0;
}
}
}
核心逻辑
import { useAnimation, setAni, Animation, GestureState } from './method'Page<{ pos: Animation }, any>({ /** * 页面的初始数据 */ data: { containers: [ 'burlywood', 'blue', 'cyan', 'black' ], zIdnexes:[], current:0, partContentList:[] }, /** * 生命周期函数--监听页面加载 */ onLoad() { this.initNode() // 当前node的下标 this.active = wx.worklet.shared(0) // 当前contentList的下标 this.current = wx.worklet.shared(0) this.zIndex = 100000 }, initNode() { // 用与保存shared值 this.Nodes = {} // 图片文件 this.contentList = [ 'https://i.hexuexiao.cn/up/ca/63/4a/a32912fc26b8445797c8095ab74a63ca.jpg', 'https://th.bing.com/th/id/OIP.kSrrRGx6nqOgWzbaEvVD9AHaNK?pid=ImgDet&rs=1', 'https://img.zmtc.com/2019/0806/20190806061552744.jpg', 'https://img.zmtc.com/2019/0806/20190806061000600.jpg', 'https://img.ratoo.net/uploads/allimg/190523/7-1Z5231J058.jpg', 'https://th.bing.com/th/id/R.47de9dfcc25d579d84850d4575d24a6a?rik=%2fGkmrewzIEY4Iw&riu=http%3a%2f%2fimg3.redocn.com%2ftupian%2f20150930%2fqizhimeinvlisheyingtu_5034226.jpg&ehk=rG9Ks2QRzj81mZl38gVGmWVAgCHVLWppoDezpfwdxjo%3d&risl=&pid=ImgRaw&r=0', 'https://th.bing.com/th/id/R.95f8e6f6bd5b660ae3ad4f3e0d712276?rik=ELKcha%2bE5ryuiw&riu=http%3a%2f%2f222.186.12.239%3a10010%2fwlp_180123%2f003.jpg&ehk=mVN7AzIRR%2fmVPJYWrWOFbEiher3QWtwSdH%2f%2fe4lE7n8%3d&risl=&pid=ImgRaw&r=0' ] this.data.containers.forEach((_: string, index: number) => { if (index == 0) { this.Nodes[`#container-${index}`] = useAnimation(`#container-${index}`, { x: 0, y: 0 }, this) this.setData({ [`zIdnexes[${index}]`]:100000-index, [`partContentList[${index}]`]:this.contentList[index] }) } else { console.log('10123') this.Nodes[`#container-${index}`] = useAnimation(`#container-${index}`, { x: 0, y: 20, scale: 0.95 }, this) this.setData({ [`zIdnexes[${index}]`]:100000-index, [`partContentList[${index}]`]:this.contentList[index] }) } }); }, handlePan(evt: any) { 'worklet'; console.log(evt) const now = this.Nodes[`#container-${this.active.value}`] as Animation const next = this.Nodes[`#container-${(this.active.value+1)%4}`] as Animation if (evt.state == GestureState.ACTIVE) { // 滑动激活状态 // 设置当前的滑动块 now.x.value += evt.deltaX now.y.value += evt.deltaY now.rotate.value = now.x.value * 10 / 360 // 设置下一个滑动块 let rate = Math.abs(now.x.value) / 150 rate = rate > 1 ? 1 : rate next.y.value = (20 - rate * 20) < 0 ? 0 : (20 - rate * 20) next.scale.value = 0.95 + rate * 0.05 } if (evt.state == GestureState.END) { // 滑动结束 if (Math.abs(now.x.value) < 150) { // 判断是否超过界限值 setAni(now.x, 0) setAni(now.y, 0) setAni(now.rotate, 0) } else if (now.x.value < 0) { // 判断判断左划还是右划 setAni(now.x, -2000) setAni(now.y, -2000) setAni(now.rotate, 0) // 通知js线程进行数据的更新 wx.worklet.runOnJS(this.toNext.bind(this))() } else if (now.x.value > 0) { setAni(now.x, 2000) setAni(now.y, -2000) setAni(now.rotate, 0) wx.worklet.runOnJS(this.toNext.bind(this))() } } }, // 将当前序号的跳转到下一个 toNext(){ const current = this.current.value+1 this.active.value = current%4 this.current.value = current this.setData({ current }) if(current-2>=0){ wx.worklet.runOnUI(this.toReset)((current-2)%4) this.setData({ [`zIdnexes[${(current-2)%4}]`]:99998-current, [`partContentList[${(current-2)%4}]`]:this.contentList[current+2] }) } }, // 将动画归位 toReset(index:number){ 'worklet'; const reset = this.Nodes[`#container-${index}`] as Animation setAni(reset.x, 0,0) setAni(reset.y, 20,0) setAni(reset.rotate, 0,0) setAni(reset.scale, 0.95,0) } })
skyline worklet 动画
联系客服