Widget开发项目案例:3D效果动画的性能优化实践
如何优化Widget中的动画以提高页面性能,对理解Widget的基本运行原理和性能优化有启发。
01
背景
这个项目的诉求是实现地球的3D效果,支持旋转动画效果,但是小程序不支持webGL
。我们曾经考虑过使用webview,但是这个产品包含动态共享文案,webview开发的工作量比较大。最终的解决方案是通过在小程序中逐帧播放图像来模拟3D旋转的效果。
经过考虑,最终图片数量为:144
png
格式图片。每张图像大约被压缩30k
,最终结果是每125ms
改变一张图像。这里要考虑两个难点:一是144
图片的预加载(图片预加载方案),二是这144
图片的处理方法(图片显示方案)
02
图片预加载
公式
什么是图像预加载?所谓预加载,就是提前加载图片,将其存储在本地的缓存中,这样当应用程序使用该图片时,可以直接从缓存中检索出来,相应的响应速度也更快,就像我们吃饭的时候一样。自助餐,我们提前把食物送到餐桌上,让我们吃起来更方便。
我们知道,在传统的Web中我们经常可以通过 1。隐藏页面上的图片并监听load事件来缓存图片。示例代码如下: 2。通过微信提供的API预加载图片进行缓存。 上面列出了这两个问题的解决方案。首先我们看一下图片预加载方案在实际操作中的优缺点: 预加载图片的两种方案( 在实际处理过程中,笔者发现,在 (注: 但是按照官网的说法,小程序会回收整个页面,但这并不在现实,所以我猜 那么 经过上面的分析,我们基本可以断定,掉帧的原因并不是在图片预加载的方式上。可能与图像显示系统有关。接下来我们看一下图像显示模型: 03 图像显示 图像显示模型主要分为两类。一种是使用 1。使用canvas绘图 这个方案在实际操作过程中,我们注意到框架脱落了。主要原因是绘图需要先加载图像。下载下来然后画出来。即使我们先加载图片,直接使用本地路径进行绘制,也存在明显的闪烁问题。另外, 其中第一个是经常遇到的限制。想象一下,您的主页在 2。仅显示当前图像组件,并通过更改组件的图像链接来更改图像。示例代码如下: 3。列出所有元素并通过控制所有图像的可见性来更改图像。示例代码如下: 1。只需显示当前图像组件并通过更改组件的图像链接来替换图像即可。 根据之前预加载方案的结论,我们可以说方案一是不可能的。 结论:问题的根本原因是小程序和微信的缓存机制对图片的回收,导致切换图片链接时出现无法控制的掉帧、闪屏。 2。列出所有元素并通过调整图像的可见性来更改图像。 看起来我们只有选项2,但选项2需要 ① ② 渲染存在延迟,因为 官网上的解释很长。其实我感觉初学者一定看不懂。这其实要从小程序的运行环境说起。小程序的运行环境分为渲染层和逻辑层。 如上,每当我们在逻辑层(JS代码)执行 在传统的Web应用中,我们可以直接编辑 所以我们需要提高Android上的页面性能,启用硬件加速就成了一条捷径。我们将上面的代码改成如下方式: 开启硬件加速后,Android不会卡顿,但页面可以正常旋转、左右滑动。但是… Apple 这个 04 调优 自定义组件 来自基础库 从版本1开始,widget开始支持组件编程,对于动画组件也有独特的优势。官网是这样说的: 小程序的自定义组件有一个单独的构造函数用于实例化 WXS响应事件 事实上,在传统的 Web 开发中,通常从图像创建交互式动画是相对常见的。很多情况下,直接使用 ,在中WXS 05 结论 综上所述,通过预加载和调整每张图片的可见度我们解决了图片闪烁的问题,通过自定义组件和开启GPU加速我们解决了动画滞后的问题。动画的最终解决方案是使用自定义组件,使用 这就是动画最终的技术方案。在这个过程中,我们列出了很多其他的解决方案,也可以作为参考。在某些业务场景下它们可能是更好的解决方案。上述解决方案更多地侧重于依赖小程序的某些功能。除了小程序本身之外,关于图片的东西就没有太多了,比如图片格式(Image
对象来预加载图像,但是小程序没有Image
对象,所以我们必须解决它。 。虽然小程序不提供 Image
对象,但它确实有一个可以使用的 。当然,换个思路,我们也可以在页面和显示器上隐藏几个
image
组件 image 组件的加载方式用于预加载。以下是这两个解决方案的示例代码:
<image
hidden
wx:for="{{preLoadImageList}}"
wx:key="{{index}}"
data-id="{{item}}"
bindload="imageOnload"
binderror="imageError"
/>
({
// 仅为示例,并非真实的资源
url: '',
success(res) {
// 图片临时路径
console.log()
}
})
提案注意事项
组件和组件其实没有本质区别)图像),但在某些情况下使用键进行预加载可以更方便地处理我们的业务逻辑。
iOS
系统(普通Android系统)中,通过隐藏image❀来预加载图像的方式效果更好。失控。所谓不受控制是指当我们的
image
组件的hidden
属性设置为true时,部分时间图像会被回收。另外,作者测试发现,当组件
线程延长了逻辑层与页面的通信时间。当视图层接收到数据消息时,距离发送时间已经过去了数百毫秒,渲染结果不是实时的; display
属性设置为none
时,也会出现同样的结果。这也进一步说明,使用组件的 hidden
属性处理小程序确实会更改组件的 display
属性,这意味着该元素仍然存在于 DOM 树中。至于图片回收,官网其实也有解释:iOS
程序小页面由多个WKWebView组成。当系统内存紧张时,部分
或者缓存时间为过短。 WKWebView
会被回收。从之前分析的案例来看,使用大图片和长列表图片会导致WKWebView
回收。 WKWebView
是iOS系统渲染层的运行环境)WKWebView
处理这个问题的方法是当图像display
属性为image❀none
时不缓存这些图像API 和
display
是否可以缓存并不重要。这不是绝对的。在实际操作过程中,我发现它和第一种解决方案没有什么不同。在iOS
操作系统中,仍然会时不时地回收一些图像,导致我们的应用程序丢帧。这个掉帧是因为图像要重新加载,而在这个过程中图像组件经历了从无到图像加载结束的过程,所以出现了明显的掉帧。微信对这方面的处理看起来有点类似于WKWebView
。它们都遵循一个原则,即当图像在用户界面中不可见时,不缓存或者缓存时间很短。方案
画布绘制
,另一种是编辑DOM
。修改 DOM 有两种方式,一种是通过单个元素image
组件的图片链接来改变图片。一种类型显示 144 个元素并控制 144 个元素的可见性以更改图像。让我们一一看看这三种解决方案:canvas
的绘制限制也会更多。 canvas
组件是小程序的原始组件。所谓原生组件,是指客户创建的脱离于WebView
渲染过程的组件。原生组件有很多限制,比如:z-index
让普通组件覆盖原生组件; 位置:固定
; overflow:hidden
来限制原有组件的显示区域; 画布
上绘制了一个元素,但某些运动逻辑需要弹出窗口。这非常棘手,fabric
覆盖了弹出窗口。无论你如何设置弹出窗口z-index
,都没有效果。当然,小程序团队也不是没有道理。他们提供了一个称为封面视图和封面图像的组件来覆盖原始组件。但coverview
有一个很大的限制:它只支持嵌套的coverview
、coverimage。 (更多关于封面视图限制的详细信息可以查看官网,这里不再详细介绍)。因此,面料
模式下的不适合本项目的需要。
<image
catchtouchstart="touchStart"
catchtouchmove="touchMove"
catchtouchend="touchEnd"
/>
<view
catchtouchstart="touchStart"
catchtouchmove="touchMove"
catchtouchend="touchEnd"
style="background-image:url({{imageUrl}})"
/>
<view
catchtouchstart="touchStart"
catchtouchmove="touchMove"
catchtouchend="touchEnd"
>
<image
wx:for="{{imageList}}"
wx:for-index="idx"
wx:key="{{idx}}"
data-id="{{item}}"
bindload="imageOnload"
binderror="imageError"
style="transform: {{idx === index ? 'translate(0, 0)' : 'translate(-100%, 0)'}};"
/>
view>
提案说明
143
更多节点。在小程序setData
中,性能与页面的节点数成正比。节点越多,理论上setData
的效率就是一次。但也没有更完美的解决办法了……我只能硬着头皮了。将代码更改为上面的第二种方案后,iOS
系统非常流畅,没有掉帧或闪烁。不过安卓基本卡住了,无法查看。页面滑动也是一个问题。如果滑动它,需要几秒钟的时间。回应来了,其实也在意料之中。 setData
本身就承载了比较大的数据量(页面还有其他逻辑数据),节点数量也成倍增加。对性能的影响是呈指数级爆炸性的。增加。官方还有一个解释: Android
用户滑动时感觉卡住,动作反馈严重延迟,因为JSJS失败了。以便及时完成手续。用户动作事件转发到逻辑层,逻辑层无法及时将动作处理结果传递到视图层;
WebViewWXML
模板和 WXSS
样式在渲染层中工作,JS 样式在渲染层中工作。通信
setData
时,它首先会从逻辑层传递到原生
。渲染层(视图层)Nativen
进行处理。好了,既然知道了渲染层和逻辑层是什么,那么跳转到上面的官网解释就很顺利了。 DOM
某个元素,也就是路径结束的时候。这不适用于小程序。没有办法一路走下去。 Native
充当转发的“桥梁”。这就是我们觉得Android被卡住的根本原因,因为它走了“弯路”。 <view
catchtouchstart="touchStart"
catchtouchmove="touchMove"
catchtouchend="touchEnd">
<image
wx:for="{{imageList}}"
wx:for-index="idx"
wx:key="{{idx}}"
data-id="{{item}}"
bindload="imageOnload"
binderror="imageError"
style="transform: {{idx === index ? 'translate(0, 0)' : 'translate(-100%, 0)'}};"
/>
view>
iOS
又回到了旧的疯狂闪屏…其实代码只是translate3d
和之间的区别。查找相关问题:关于translate3D动画在iphone上闪烁显示问题,简单来说,这个问题的根本原因是
。 (当该值在组件之间传递时,会发生深度复制。) WebKit
分配了translate3d
进行渲染的元素,但是它的inner child元素不渲染。 ,解决方案是缓存子元素并使它们成为单独的层。然而,小程序的image
组件实际上是一个多层的DOM
结构。假想结构如下: <div style="display:inline-block; ...">
<div style="display:inline-block;background-image:url({{url}});...">
...
div>
div>
image
组件无法在小程序中控制。子元素,通过提取当前用户操作系统来决定是否开启GPU加速的变通办法,即是否使用translate3d
; setData
连续改变用户界面也可以实现动画效果。这允许您任意更改界面,但通常会导致较大的滞后或挂起,甚至小程序崩溃。目前,您可以通过将页面的 setData
自定义组件更改为 setData
来提高性能。 组件
时,setData
文件的内容不会直接深复制,即♹,即之后的❀♹ 这个。 data.field === obj Component
,因此它的 setData 和 坺
位于主页面互不影响。确实非常适合应用到这个业务场景。图像切换和滑动逻辑可以封装为单独的组件,然后呈现在主页上。这样既不会影响主页面的性能,也可以为这部分数据变化提供更快的响应时间。 WXS
是小程序自带的脚本语言。语法与JS
基本相同,可以直接嵌入WXML
。我们的界面说JS
和WXML
通信需要NativeNative
这个“桥梁”无法直接完成❙♹。 活动。 WXS
可以直接作用于DOM
。 DOM
。小程序则不同。小部件没有 DOM API
。现在网上很多人都说小部件没有DOM
。这其实是不准确的。请记住:小部件具有 DOM
,但 DOM API
和 BOM API
不存在。因此,它不能直接使用jQuery
等库。WXS
的应用原理也是基于此。我们不再继续setData
,即JS
和WXML
我们可以编辑DOM
树
,但 WXS 响应来自小程序基础库的事件cai 出于兼容性原因,本示例中未使用此方案。关于如何修改
WXS
DOM
,请参见官网WXS响应事件,此处不再赘述。 预加载图像,列出所有元素,并控制每个图像的可见性,以达到切换图像的目的。另外,系统会通过评估Android和
iOS
来决定是否启用GPU
加速。 webp
、apng
形状的图片)、大小(是否还有可折叠的)模式)等
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。