- 小程序横屏兼容处理
背景 在h5开发中可用的css适配单位有 em/百分比/rem/vw/vh/vmin/vmax,小程序提供了 rpx: 可以根据屏幕宽度进行自适应,规定屏幕宽为750rpx。 日常小程序开发中,一般设计图尺寸为 750 * 1334 px,则在小程序中一般 1px 直接写为 1rpx ,当小程序为竖屏([代码]"pageOrientation": "portrait"[代码] 默认为竖屏)时,根据 rpx 可以直接还原UI图,但是当小程序为横屏([代码]"pageOrientation": "landscape"[代码])时,根据 rpx 适配就明显比较大,不符合UI图,因为 rpx 是根据屏幕宽度适配的。 这个时候就需要一种既能适配不同屏幕大小,又能以设计图为准,快速布局的方式。通过了解 em/百分比/rem/vw/vh/vmin/vmax 这几种方式,明显 vmin 更符合,vmin 是vw和vh中比较 小 的值。 vw: Viewport宽度, 1vw 等于viewport宽度的 1%。 vh: Viewport高度, 1vh 等于viewport高的的 1%。 所以 100 vmin = 750px。 [代码].wxss[代码] 文件处理 当设置某个元素的宽度为 100px 时,根据 [代码]100px / 750px = x / 100vmin[代码] ,则对应的 vmin 值为 100vmin / 7.5 ,当单位为 rpx 时, vmin 值为 100vmin / 7.5 ,即 [代码]100px = 100vmin / 7.5[代码] 或者 [代码]100rpx = 100vmin / 7.5[代码] ,但是每次都写 [代码]vmin / 7.5[代码] 又有点麻烦,所以就写了个小工具 rpx2vmin ,支持将 rpx/px 转译为 vmin,这样布局的时候依然写 rpx/px ,最后再转译一下就可以了。 将需要转译的 [代码].wxss[代码] 文件复制粘贴到 [代码]input[代码] 文件下,在项目目录下执行如下命令行 ,会在 [代码]ouput[代码] 目录下生成对应的文件名称,需要提前安装 nodejs。 [代码]# 安装依赖 npm install # 将 rpx 转译为 vmin npm run rpx2vmin # 将 px 转译为 vmin npm run px2vmin [代码] 主要处理的如下: [代码]font-size: 12rpx; height: 60rpx; padding: 12rpx 16rpx; border-left: 2rpx dashed #5DA5FF; width: calc(100vw - 50rpx - 80rpx); [代码] 转移为 [代码]font-size: calc(12vmin / 7.5); height: calc(60vmin / 7.5); padding: calc(12vmin / 7.5) calc(16vmin / 7.5); border-left: calc(2vmin / 7.5) dashed #5DA5FF; width: calc(100vw - 50vmin / 7.5 - 80vmin / 7.5); [代码] 或者是: [代码]font-size: 12px; height: 60px; padding: 12px 16px; border-left: 2px dashed #5DA5FF; width: calc(100vw - 50px - 80px); [代码] 转移为 [代码]font-size: calc(12vmin / 7.5); height: calc(60vmin / 7.5); padding: calc(12vmin / 7.5) calc(16vmin / 7.5); border-left: calc(2vmin / 7.5) dashed #5DA5FF; width: calc(100vw - 50vmin / 7.5 - 80vmin / 7.5); [代码] js 中的处理 某些时候我们可能需要通过 js 计算设置,这个时候可以通过 wx.getSystemInfo() 得到 [代码]windowWidth[代码](可使用窗口宽度,单位px) 和 [代码]windowHeight[代码](可使用窗口高度,单位px) , [代码]100vmin = Math.min(windowWidth, windowHeight)[代码], [代码]1px = Math.min(windowWidth, windowHeight) / 750[代码] ,其中750为布局的时候可视窗口的最小宽度,其他尺寸乘以比例即可得到对应的 px 值或者 rpx 值。
2022-04-12 - textarea的穿透问题,什么时候可以解决?
textarea的穿透问题,什么时候可以解决?
2020-04-22 - scroll-view组件,scroll-into-view导致页面空白怎么解决?
具体描述: scroll-view组件,下拉加载数据时,动态设置scroll-into-view导致页面空白 代码片段: https://developers.weixin.qq.com/s/x8Jw0Mmk7aHN 页面空白表现: [视频] 设备信息: System: iPhone, iOS 16.0.3, WeChat 8.0.34 Network: wifi UA: Mozilla/5.0 (iPhone: CPU iPhone OS 16 0 3 like Mac OS X)AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.34(0x18002234) NetTypeWIFI Language/zhCN WeChatLib: 2.31.0 (2023.4.23 11:19:33)
2023-04-27 - 安卓部分视频播放卡顿
- 当前 Bug 的表现(可附上截图) 部分视频在安卓手机上播放卡顿,在IOS上无异常。测试了三星S10+,华为Mate20 Pro,坚果Pro2 - 预期表现 所有机型均能正常播放 - 提供一个最简复现 Demo 见代码片段
2019-05-09 - Component中的properties支持多种类型吗?
加入父组件传给子组件一个属性 sayType ,既可能是数组,也可能是字符串,代码层面应该如何书写? 可以类似下面这样写吗 Component{ properties: { sayType: string/Array, value: ''/[] } }
2021-03-15 - video视频 进度条灰色预加载条左右抖动?
video组件,部分 iphone 手机会有左右抖动,可以帮忙看看吗? https://weddingbo-1256393015.cos.ap-chengdu.myqcloud.com/4424/case/Image/2022-11-1/81e12196-4ea4-8c80-d626-3032e78d3558.mp4 [图片]
2022-11-01 - wxml-to-canvas 使用规则
先说结论: 仅适用生成简单布局的页面,复杂页面会有大量性能开销。 复杂页面可以其他使用webview嵌入的方式用原生H5代替。 安装使用见官方文档,本文主要提及重要使用规则。 需要为wxml-to-canvas指定width和height属性,默认为400*300,当该尺寸与wxss最大容器尺寸不一致时会有异常只能使用view、text、image标签view只用于布局、text只用于文字、image只用于图片(可绝对定位作为背景)text、image必须指定width、heightview可以不指定height、只指定width,由子元素动态撑高。渲染高度限制:IOS实际像素 < 4096,实际高度需 < 4096/3 = 1356动态数据可通过函数结合模板字符串实现画布高度最好是确定的数字(<1356)lineHeight可使text文本居中,lineHeight值等于height即可多个absolute元素时,因为没有z-index,template元素自上而下渲染,对应z-index依次增高元素默认为flex布局,子元素会继承父元素的flex布局属性(row或column)背景颜色仅可使用backgroundColor,而非background。性能优化指南 在canvas绘制完成后,可以使用this.widget.canvasToTempFilePath来保存并获取本地临时路径,但是图片可能会比较大,通常会1-2M,实际我们应该尽可能在保证清晰的情况下减小文件大小,我们可以通过canvasToTempFilePath的参数配置来进行调整,如下通常可以保持较小的体积及基本合适的清晰度。更多的配置可详细参考canvasToTempFilePath文档{ destWidth: canvas组件的宽度, destHeight: canvas组件的高度, fileType: 'jpg', quality: 0.9 } 以下是动态数据与动态高度及基本使用的示例: //动态wxml : poster-data.js const getWxml = (info)=>{ return `${info.a?`${info.b}`:''}` } //动态css const getStyle = (info)=>{ return { textTest: { width: 100, height: 20, fontSize: info.fontSize } } } //页面使用 : index.js const { wxml, style } = require('./poster-data.js'); //... data:{ canvasHeight: 0, info:{ a:1, b:2, fontSize: 16 } } onLoad(){ const tempHeight = this.computeContentHeight() this.setData({ canvasHeight:tempHeight },()=>{ this.widget = this.selectComponent('.widget');//数据就绪后canvas高度已确定,再获取canvas组件实例 }) } computeContentHeight(){ //do something 根据数据动态现实隐藏而修改canvas高度 return 500; } saveImage(){ wx.showLoading({ title: '生成中请稍后.', mask: false, }); const template = getWxml(this.data.info);//获取template const css = getStyle(this.data.info)//获取canvas const p1=this.widget.renderToCanvas({wxml:template,style:css}); p1.then((res)=>{ const p2 =this.widget.canvasToTempFilePath();//canvas图片保存到本地临时路径 p2.then(res=>{ wx.hideLoading(); this.data.poster = res.tempFilePath // 使用微信分享 wx.showShareImageMenu({ path: res.tempFilePath }); // 或保存到本地 // wx.saveImageToPhotosAlbum({ // filePath: res.tempFilePath, // success(res){ // console.log(res); // }, // fail(res){ // console.log(res); // } // }); }); }); } //隐藏canvas .widget { position: absolute; left: -999px; } 参考 小程序中实现页面截图 | 微信开放社区wxml-to-canvas 组件中高度不能超过 4096?,否则无法生成图片,报错 | 微信开放社区微信扩展组件wxml-to-canvas支持动态传入数据吗?急 | 微信开放社区小程序使用wxml-to-canvas截屏_培根芝士的博客-CSDN博客_小程序 截屏
2023-08-01 - MapContext.moveToLocation 无效的问题,是不是权限申请导致?
代码如下 var mapCtx = wx.createMapContext("map"); //wxml中map标签的id值 mapCtx.moveToLocation(); XML内的map地图已经加上了 show-location="true" 尝试用按钮触发 moveToLocation 编辑器上的模拟器 可以正常移动到 个人中线点位置 产品上线后 以及 预览中 是无法移动的 通过查看文档 https://developers.weixin.qq.com/miniprogram/dev/api/media/map/MapContext.moveToLocation.html 也没有说明 需要申请权限之类的 请问是怎么回事?
2023-02-23 - 【小程序代码自查】小程序闪退-内存泄露导致
背景用户经常出现闪退的情况,并提示内存不足。根据用户操作场景,猜测页面存在内存泄露。 内存泄露是什么?内存泄露是程序运行过程中产生的内存变量会一直存在,不会被垃圾回收机制检测到,导致一直不会被销毁,内存占用会越来越大。 比如说: 我们在运行小程序的时候会产生一个页面,小程序会给这个页面创建一个实例,当这个页面销毁的时候,这个实例应该会被销毁。 但是如果我们有个定时器(setInterval),定时器里面对这个页面实例存在引用,那这个页面实例就不会被销毁,因为有被用到。 当存在内存泄露的情况,用户长期使用我们的小程序会导致小程序占用的内存越来越大,最后会导致小程序闪退(被微信强制销毁) 排查内存泄露用到的工具-weakSet先简单描述一下weakSet,让大家有个简单的认识,详细需要去看下文档。 weakSet 是一个可以存储唯一变量的集合,和Set不一样的是,weakSet存储的变量都是弱引用,就是不会影响垃圾回收,如果存储的变量被回收了,在这个集合里面就找不到。 所以weakSet不能被遍历,也没有长度的概念。但是我们可以通过控制台打印weakset的指向,知道里面有多少个元素。如下图: [图片] 通过展开,我们可以知道里面是哪个页面的实例,但是我们在控制台展开就意味着我们对这个页面实例存在引用,则无法被垃圾回收。所以在执行垃圾回收之前需要清空控制台的输出。 如何确定页面是否存在内存泄露如果页面存在内存泄露则不会销毁页面实例。我们只需要判断页面实例有没有被销毁即可。 我们在一开始就把页面实例加到weakSet里面,当执行多次跳转页面之后,会存在多个页面实例,最后回到首页,触发小程序的垃圾回收。 如果不存在内存泄露,那weakSet集合里面只会存在两个页面实例(当前页面实例+返回回来的页面实例),比如下图的页面A和页面B。 如果存在内存泄露,那weakSet集合里面会存在多个页面实例(当前页面实例+存在内存泄露的页面实例*n),比如下图的页面A、页面B、页面C和页面D. 具体如下图: [图片] 如何主动触发小程序的垃圾回收小程序没有api可以让我们触发小程序的垃圾回收,我们目前可以通过开发者工具的performance面板或memory的垃圾回收(collect garbage 垃圾桶图标)按钮。 [图片] [图片] 触发垃圾回收之后的结果如图: [图片] 这个需要手动触发才可以,我们在测试的时候需要手动点击,无法自动触发,所以我们想了个方案自动触发垃圾回收。 通过给内存塞很多数据,然后将这些数据标为无用的,当内存达到500m左右小程序就会触发垃圾回收。这个办法会导致我们内存一段时间激增,建议尽量在跳转页面的时候不要开启,只有在最后页面跳转回首页才进行。 // 主动触发垃圾回收 setInterval(()=>{ if(!global.startGC){ return } let a = [] for (let i = 0; i < 10000000; i++) { a.push({ name: "pling", age: Math.random() * 10000 }) } console.log("length", a.length) a = [] }, 3000) 如何定位页面内存泄露的原因内存泄露的情况举例: global.list = [] Page({ // ... onLoad() { // ... 省略其他代码 // 将页面实例挂载到全局对象,如没有清理,则页面实例会一直不被销毁 global.list.push(this) // 存在Interval计时器,则会一直存在对页面实例的引用 setInterval(() => { console.log("test", this.data) }, 5000); // 通过settimeout的循环调用,实现了类似于interval的效果也会导致页面实例不会被销毁 this.testLoop() const that = this function test(){ console.log(that.data) } // 将内部函数挂载到全局变量,则会导致函数的作用域链都会存在引用,不会被销毁 global.logThis = test }, testLoop(){ setTimeout(() => { this.testLoop() }, 10000); } }) 通过上面我们可以知道一般会有上面四种情况导致内存泄露。 将对象挂载到全局对象上,页面写在没有清楚通过暴露内部函数给外部对象,导致存在作用域的引用,页面卸载没有清楚内部函数存在定时执行的函数存在对页面实例的引用,页面销毁没有清除定时器通过延时执行的函数循环调用,并存在对页面实例的引用,页面销毁没有停止调用。第一第二种情况会比较少出现,目前暂时还没考虑如何去排查。 第三第四种都会对页面实例存在调用,所以我们在页面实例销毁之后对页面实例上的属性进行监听,如果一直存在调用则会有问题。 [图片] 具体实现代码: // 检查页面卸载后对页面实例调用 Page({ data: { test: "111" }, onLoad() { global.pageSet.add(this) setInterval(() => { console.log("test", this.__wxExparserNodeId__, this.data.test) }, 5000); }, // .... onUnload(){ console.log("unload"); const that = this // 获得可以枚举的属性列表 const keys = Object.keys(that) // 加入data 因为data 不是可以枚举的属性 keys.push("data") console.log(keys); keys.map(key=>{ // 获得原本的属性描述 const property = Object.getOwnPropertyDescriptor(that, key) // 保留原有的值 const origin = that[key]; // 获得属性的get方法 有可能没有 const getter = property && property.get // 获得属性的set方法 有可能没有 const setter = property && property.set const isFunction = typeof origin === "function" // 如果是function的话 需要绑定this if(isFunction){ origin.bind(that) } const newThis = {} // 拦截属性 Object.defineProperty(that, key, { get: function(){ console.log(`调用了this.${key}的getter`); // 有getter 调用getter if(getter){ return getter.call(that) } return newThis[key] || origin }, set: function(newVal){ console.log(`调用了this.${key}的setter`); if(setter){ return setter.call(that, newVal) } newThis[key] = newVal } }) }) } }) 测试demo我们在自己项目里面测试会比较麻烦,一开始可能会有干扰,所以我这边弄了个代码片段,先校验一下这个方法是否可行,如果可行再加到自己的项目里面。 小程序代码片段
2021-05-12