来源:微信开发者
2023-05-04 15:52:41
(2)在 wxss 中,设置图片查看页面的 page 节点为透明背景{\"backgroundColor\": \"#00000000\",\"backgroundColorContent\": \"#00000000\", // 设置客户端页面背景为透明\"navigationStyle\": \"custom\",\"renderer\": \"skyline\", // skyline渲染引擎\"disableScroll\": true,\"usingComponents\": {}}
(3)在 js 中,使用 wx.router.addRouteBuilder(routeType, fn) 来声明自定义路由动画page {background: transparent;}
(4)在图片列表页面中,使用 wx.navigateTo 来跳转页面,并且设置 routeType 为 myCustomRoutewx.router.addRouteBuilder("myCustomRoute", function (params) {const handlePrimaryAnimation = () => {"worklet";return {// 可在此处,根据 params.primaryAnimation.value 的值,来设置页面的动画效果backgroundColor: `rgba(0,0,0,${ params.primaryAnimation.value })`};};return {opaque: false,handlePrimaryAnimation,barrierColor: "",barrierDismissible: false,transitionDuration: 320,reverseTransitionDuration: 250,canTransitionTo: true,canTransitionFrom: false};})
需配置页面的渲染引擎为 Skyline,并且在跳转时使用 routeType 就可以实现让页面在跳转时没有默认的路由动画。wx.navigateTo({url: "/pages/skyline-image-viewer/index?index=0",routeType: "myCustomRoute"})
这时,界面的表现像上面视频一样,是一个连续的动画状态,这完全是由 share-element 来控制的,share-element 的动画原理如下图所示:you code here
这里,我们使用小程序渲染框架提供的
const GestureState = {POSSIBLE: 0, // 此时手势未识别BEGIN: 1, // 手势已识别ACTIVE: 2, // 连续手势活跃状态END: 3, // 手势终止CANCELLED: 4 // 手势取消};Component({attached() {this.shareX = wx.worklet.shared(0);this.shareY = wx.worklet.shared(0);this.sharScale = wx.worklet.shared(1);// 声明共享变量,并且给需要变化的dom,绑定动画this.applyAnimatedStyle(".current-item", () => {"worklet";return {transform: `translate3d(${this.shareX.value}px, ${this.shareY.value}px, 0) scale(${this.sharScale.value})`}});// 页面所需的数据,需要在 attached 事件里初始化完毕,使其可以参与首帧渲染this.setData({ src: "...", shareKey: "..." });},methods: {// 当手势组件识别到手势时,触发此回调onScaleGestureHandle(e) {"worklet";const { state } = e;// 在worklet函数里,不要使用 const {} = this 对this解构const shareX = this.shareX;const shareY = this.shareY;const sharScale = this.sharScale;if (state === GestureState.BEGIN) {// 手势已经识别,此时,可以获取到手势的初始值} else if (state === GestureState.ACTIVE) {// 手势活跃状态,此时,可以获取到手势的变化值,如平移的距离、缩放的比例等// 将当前变化的值,设置到 `shared` 变量,就可以改变元素的样式,类似于vue3的数据驱动shareX.value += e.focalDeltaX;shareY.value += e.focalDeltaY;sharScale.value = e.scale;} else if (state === GestureState.END || state === GestureState.CANCELLED) {// 手势终止或取消,此时,可以获取到手势的最终值}}}})
什么是手势协商?
(资料图)
手势协商指的是:当页面同时有多个手势交互时,需通过一定的约定来决定哪些手势事件应该被执行,哪些需要被忽略。
小程序渲染框架解决手势冲突的方式,主要是通过手势组件的 tag、simultaneous-handlers、native-view 和 should-response-on-move 来实现
tag:手势组件的标识,用于区分不同的手势组件
simultaneous-handlers:手势组件的协商者,表示需要同时触发事件的手势组件的标识
should-response-on-move:参与手势时间的派发过程,返回 false时,表示该手势时间不会继续派发
native-view:用当前手势组件来代理原生组件内部的手势事件,如
simultaneous-handlers=\"{{["swiper"]}}\"worklet:ongesture=\"onScaleGestureHandle\">native-view=\"swiper\"simultaneous-handlers=\"{{["scale"]}}\"worklet:should-response-on-move=\"shouldResponseOnMove\">
const GuestureMode = {INIT: 0,SCALE: 1,SWIPE: 2,MOVE: 3// ...};Component({attached() {this.GuestureModeShared = wx.worklet.shared(GuestureMode.INIT);this.shareX = wx.worklet.shared(0);this.shareY = wx.worklet.shared(0);this.shareScale = wx.worklet.shared(1);// 声明共享变量,并且给需要变化的dom,绑定动画this.applyAnimatedStyle(".current-item", () => {"worklet";return {transform: `translate3d(${this.shareX.value}px, ${this.shareY.value}px, 0) scale(${this.shareScale.value})`}});// ...},methods: {onScaleGestureHandle(e) {"worklet";const { state } = e;if (state === GestureState.BEGIN) {this.GuestureModeShared.value = GuestureMode.INIT;} else if (state === GestureState.ACTIVE) {if(this.GuestureModeShared.value === GuestureMode.INIT) {this.gestureBefore(e); // 手势类型未知时,判断手势类型} else {this.gestureHandle(e); // 手势类型已知时,处理手势事件}} else if (state === GestureState.END || state === GestureState.CANCELLED) {this.GuestureModeShared.value = GuestureMode.INIT;}},// 判断手势类型gestureBefore(e) {"worklet";const { focalDeltaX, focalDeltaY, scale } = e;if (Math.abs(focalDeltaX) > Math.abs(focalDeltaY)) {this.GuestureModeShared.value = GuestureMode.SWIPE;} else if (scale > 1) {this.GuestureModeShared.value = GuestureMode.SCALE;} else {this.GuestureModeShared.value = GuestureMode.MOVE;}},// 处理手势事件gestureHandle(e) {"worklet";if (this.GuestureModeShared.value === GuestureMode.SCALE) {this.shareScale.value = e.scale;} else if (this.GuestureModeShared.value === GuestureMode.SWIPE) {// swiper 切换模式时,这里什么都不用做} else if (this.GuestureModeShared.value === GuestureMode.MOVE) {this.shareX.value += e.focalDeltaX;this.shareY.value += e.focalDeltaY;}},// 用于判断手势事件是否应该派发shouldResponseOnMove(e) {"worklet";return this.GuestureModeShared.value === GuestureMode.SWIPE; // 当模式为SWIPE时,才响应手势事件}}})
通过上面的代码,我们实现了手势协商,当用户在图片上进行滑动的操作时,总是会触发
作为一款新的渲染优化方式,开发者使用小程序渲染框架需要注意以下内容,以保证渲染的效果和性能。
(1)自定义路由时首帧渲染&首帧性能优化
小程序渲染框架的首帧渲染对共享元素动画非常重要,若共享元素节点的key 错过首帧设置的话,可能会丢失飞跃动画,所以在使用小程序渲染框架时,共享元素的 key 应该尽量在 attached 中或之前设置到页面,并且在首帧渲染时,应尽可能的减少 UI 层的渲染工作 如下:
1)所需要的数据应尽可能使用提前计算好,避免构建页面时等待太久影响响应速度
2)首次设置的数据应该尽可能的少,避免首次渲染时,页面上的元素过多,导致首帧渲染时间过长,导致动画卡顿(如:不要同时初始化太多的
3)确保首帧渲染时,共享元素的 key 正确的设置,避免在首帧渲染时,由于找不到对应的共享元素,导致动画丢失,看不到飞跃动画
4)由于手势事件触发频繁,应尽量避免大量需要的计算的逻辑高频执行,容易导致机器发烫,或者导致动画卡顿
**worklet 函数的使用**
worklet 函数的使用有一些限制,主要是由于它是在 UI 线程执行的,所以 worklet 函数中的 this 并非是页面的 this 实例, 里面所使用到的变量也是通过特殊的 babel 插件转换到UI线程的,需要与逻辑层共用的变量都需要用 wx.worklet.shared 将它声明成共享变量,在 UI 线程调用逻辑层的函数需要使用 wx.worklet.runOnJS
(2)与 web 规范的差异
虽然小程序渲染框架尽可能的与 web 规范保持一致,但是由底层渲染引擎的限制,还是有一些差异,如:
1)display: flex 的默认朝向是 column,而不是 row,这需要开发者注意,官方后续会支持 block 布局方式
2)暂不支持 css 伪元素,如 ::after、::before,官方正在支持中
3)position 仅支持 absolute、relative,不支持 sticky,实现滚动吸附的效果需用 sticky-* 组件来配合 scroll-view 实现
**
在非小程序渲染框架的运行环境内,
开发时,请确保小程序开发者工具版本是,sdk 版本在 2.30.2+,具体限制可参考。
这些新特性的引入,使得小程序渲染框架在小程序开发中的优势更加明显,开发者可以更加便捷地实现各种复杂的交互效果,并且达到接近原生APP的体验。
未来展望1、个性化产品形态:将会根据不同的用户需求和场景,设计出更加符合用户喜好和习惯的动效衔接,进行组件化调用。
2、更加自然和真实的动效衔接:动效衔接将会更加贴近自然规律和真实物理效应,从而增强动画的真实感和用户体验。
3、更加智能化和自适应的动效衔接:动效衔接将会根据用户的操作行为和使用习惯,自适应调整动画效果,从而提高用户体验和产品效果。
4、扩大产品、设计与开发的协作效应:设计对动效的把控、产品对用户的洞察以及开发对新技术的应用,才可以发挥最大化的协作效应。
附1:本文作者
惠波:同程旅行研发工程师
洛奇、子聪:同程旅行体验设计师
老干部:同程旅行产品经理
附2:代码片段
相册小程序代码片段(请使用 PC 端浏览器打开):
附3:UE标注
附4:AB 实验效果
AB 实验显著win0.23%。
关键词: