- 自定义navigationBar顶部导航栏,兼容适配所有机型(附完整案例)
前言 navigationBar相信大家都不陌生把?今天我们就来说说自定义navigationBar,把它改变成我们想要的样子(搜索框+胶囊、搜索框+返回按钮+胶囊等)。 思路 隐藏原生样式 获取胶囊按钮、状态栏相关数据以供后续计算 根据不同机型计算出该机型的导航栏高度,进行适配 编写新的导航栏 引用到页面 正文 一、隐藏原生的navigationBar window全局配置里有个参数:navigationStyle(导航栏样式),default=默认样式,custom=自定义样式。 [代码]"window": { "navigationStyle": "custom" } [代码] 让我们看看隐藏后的效果: [图片] 可以看到原生的navigationBar已经消失了,剩下孤零零的胶囊按钮,胶囊按钮是无法隐藏的。 二、准备工作 1.获取胶囊按钮的布局位置信息 我们用wx.getMenuButtonBoundingClientRect()【官方文档】获取胶囊按钮的布局位置信息,坐标信息以屏幕左上角为原点: [代码]const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); [代码] width height top right bottom left 宽度 高度 上边界坐标 右边界坐标 下边界坐标 左边界坐标 下面是官方给的示意图,方便大家理解几个坐标。 [图片] 2.获取系统信息 用wx.getSystemInfoSync()【官方文档】获取系统信息,里面有个参数:statusBarHeight(状态栏高度),是我们后面计算整个导航栏的高度需要用到的。 [代码]const systemInfo = wx.getSystemInfoSync(); [代码] 三、计算公式 我们先要知道导航栏高度是怎么组成的, 计算公式:导航栏高度 = 状态栏高度 + 44。 实例 【源码下载】 自定义导航栏会应用到多个、甚至全部页面,所以封装成组件,方便调用;下面是我写的一个简单例子: app.js [代码]App({ onLaunch: function(options) { const that = this; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); // 胶囊按钮位置信息 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 状态栏高度 + 44 that.globalData.navBarHeight = systemInfo.statusBarHeight + 44; that.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right; that.globalData.menuBotton = menuButtonInfo.top - systemInfo.statusBarHeight; that.globalData.menuHeight = menuButtonInfo.height; }, // 数据都是根据当前机型进行计算,这样的方式兼容大部分机器 globalData: { navBarHeight: 0, // 导航栏高度 menuRight: 0, // 胶囊距右方间距(方保持左、右间距一致) menuBotton: 0, // 胶囊距底部间距(保持底部间距一致) menuHeight: 0, // 胶囊高度(自定义内容可与胶囊高度保证一致) } }) [代码] app.json [代码]{ "pages": [ "pages/index/index" ], "window": { "navigationStyle": "custom" }, "sitemapLocation": "sitemap.json" } [代码] 下面为组件代码: /components/navigation-bar/navigation-bar.wxml [代码]<!-- 自定义顶部栏 --> <view class="nav-bar" style="height:{{navBarHeight}}px;"> <input class="search" placeholder="输入关键词!" style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{menuHeight}}px; left:{{menuRight}}px; bottom:{{menuBotton}}px;"></input> </view> <!-- 内容区域: 自定义顶部栏用的fixed定位,会遮盖到下面内容,注意设置好间距 --> <view class="content" style="margin-top:{{navBarHeight}}px;"></view> [代码] /components/navigation-bar/navigation-bar.json [代码]{ "component": true } [代码] /components/navigation-bar/navigation-bar.js [代码]const app = getApp() Component({ properties: { // defaultData(父页面传递的数据-就是引用组件的页面) defaultData: { type: Object, value: { title: "我是默认标题" }, observer: function(newVal, oldVal) {} } }, data: { navBarHeight: app.globalData.navBarHeight, menuRight: app.globalData.menuRight, menuBotton: app.globalData.menuBotton, menuHeight: app.globalData.menuHeight, }, attached: function() {}, methods: {} }) [代码] /components/navigation-bar/navigation-bar.wxss [代码].nav-bar{ position: fixed; width: 100%; top: 0; color: #fff; background: #000;} .nav-bar .search{ width: 60%; color: #333; font-size: 14px; background: #fff; position: absolute; border-radius: 50px; background: #ddd; padding-left: 14px;} [代码] 以下是调用页面的代码,也就是引用组件的页面: /pages/index/index.wxml [代码]<navigation-bar default-data="{{defaultData}}"></navigation-bar> [代码] /pages/index/index.json [代码]{ "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" } } [代码] /pages/index/index.js [代码]const app = getApp(); Page({ data: { // 组件参数设置,传递到组件 defaultData: { title: "我的主页", // 导航栏标题 } }, onLoad() { console.log(this.data.height) } }) [代码] 效果图: [图片] 好了,以上就是全部代码了,大家可以文中复制代码,也可以【下载源码】,直接到开发者工具里运行,记得appid用自己的或者测试哦! 下面附几张其它小程序的效果图,大家也可以尝试照着做: [图片][图片] 总结 本文写了自定义navigationBar的一些基础性东西,里面涉及组件用法、参数传递、导航栏相关。 由于测试环境有限,大家在使用时如果发现有什么问题,希望及时反馈,以供及时更新帮助更多的人! 大家有什么疑问,欢迎评论区留言!
2022-06-23 - [填坑手册]小程序Canvas生成海报(一)--完整流程
[图片] 海报生成示例 最近智酷君在做[小程序]canvas生成海报的项目中遇到一些棘手的问题,在网上查阅了各种资料,也踩扁了各种坑,智酷君希望把这些“填坑”经验整理一下分享出来,避免后来的兄弟重复“掉坑”。 [图片] 原型图 这是一个大致的原型图,下面来看下如何制作这个海报,以及整体的思路。 [图片] 海报生成流程 [代码片段]Canvas生成海报实战demo demo的微信路径:https://developers.weixin.qq.com/s/Q74OU3m57c9x demo的ID:Q74OU3m57c9x 如果你装了IDE工具,可以直接访问上面的demo路径 通过代码片段将demo的ID输入进去也可添加: [图片] [图片] 下面分享下主要的代码内容和“填坑现场”: 一、添加字体 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/font.html [代码]canvasContext.font = value //示例 ctx.font = `normal bold 20px sans-serif`//设置字体大小,默认10 ctx.setTextAlign('left'); ctx.setTextBaseline("top"); ctx.fillText("《智酷方程式》专注研究和分享前端技术", 50, 15, 250)//绘制文本 [代码] 符合 CSS font 语法的 DOMString 字符串,至少需要提供字体大小和字体族名。默认值为 10px sans-serif 文字过长在canvas下换行问题处理(最多两行,超过“…”代替) [代码]ctx.setTextAlign('left'); ctx.setFillStyle('#000');//文字颜色:默认黑色 ctx.font = `normal bold 18px sans-serif`//设置字体大小,默认10 let canvasTitleArray = canvasTitle.split(""); let firstTitle = ""; //第一行字 let secondTitle = ""; //第二行字 for (let i = 0; i < canvasTitleArray.length; i++) { let element = canvasTitleArray[i]; let firstWidth = ctx.measureText(firstTitle).width; //console.log(ctx.measureText(firstTitle).width); if (firstWidth > 260) { let secondWidth = ctx.measureText(secondTitle).width; //第二行字数超过,变为... if (secondWidth > 260) { secondTitle += "..."; break; } else { secondTitle += element; } } else { firstTitle += element; } } //第一行文字 ctx.fillText(firstTitle, 20, 278, 280)//绘制文本 //第二行问题 if (secondTitle) { ctx.fillText(secondTitle, 20, 300, 280)//绘制文本 } [代码] 通过 ctx.measureText 这个方法可以判断文字的宽度,然后进行切割。 (一行字允许宽度为280时,判断需要写小点,比如260) 二、获取临时地址并设置图片 [代码]let mainImg = "https://demo.com/url.jpg"; wx.getImageInfo({ src: mainImg,//服务器返回的图片地址 success: function (res) { //处理图片纵横比例过大或者过小的问题!!! let h = res.height; let w = res.width; let setHeight = 280, //默认源图截取的区域 setWidth = 220; //默认源图截取的区域 if (w / h > 1.5) { setHeight = h; setWidth = parseInt(280 / 220 * h); } else if (w / h < 1) { setWidth = w; setHeight = parseInt(220 / 280 * w); } else { setHeight = h; setWidth = w; }; console.log(setWidth, setHeight) ctx.drawImage(res.path, 0, 0, setWidth, setHeight, 20, 50, 280, 220); ctx.draw(true); }, fail: function (res) { //失败回调 } }); [代码] 在开发过程中如果封面图无法按照约定的比例(280x220)给到: 那么我们就需要处理默认封面图过大或者过小的问题,大致思路是:代码中通过比较纵横比(280/220=1.27)正比例放大或者缩小原图,然后从左上切割,竟可能保证过高的图是宽度100%,过宽的图是高度100%。 在canvas中draw图片,必须是一个(相对)本地路径,我们可以通过将图片保存在本地后生成的临时路径。 微信官方提供两个API: wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。 三、裁切“圆形”头像画图 [代码]ctx.save(); //保存画图板 ctx.beginPath()//开始创建一个路径 ctx.arc(35, 25, 15, 0, 2 * Math.PI, false)//画一个圆形裁剪区域 ctx.clip()//裁剪 ctx.closePath(); ctx.drawImage(headImageLocal, 20, 10, 30, 30); ctx.draw(true); ctx.restore()//恢复之前保存的绘图上下文 [代码] 使用图形上下文的不带参数的clip()方法来实现Canvas的图像裁剪功能。该方法使用路径来对Canvas话不设置一个裁剪区域。因此,必须先创建好路径。创建完整后,调用clip()方法来设置裁剪区域。 需要注意的是裁剪是对画布进行的,裁切后的画布不能恢复到原来的大小,也就是说画布是越切越小的,要想保证最后仍然能在canvas最初定义的大小下绘图需要注意save()和restore()。画布是先裁切完了再进行绘图。并不一定非要是图片,路径也可以放进去~ 小程序 canvas 裁切BUG [代码]ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); //第一个填充矩形 wx.downloadFile({ url: headUri, success(res) { ctx.beginPath() ctx.arc(50, 50, 25, 0, 2 * Math.PI) ctx.clip() ctx.drawImage(res.tempFilePath, 25, 25); //第二个填充图片 ctx.draw() ctx.restore() ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); ctx.draw(true) ctx.restore() } }) [代码] clip裁切这个功能,如果有超过一张图片/背景叠加,则裁切效果失效。 错误参考:http://html51.com/info-38753-1/ 四、将canvas导出成虚拟地址 [代码]wx.canvasToTempFilePath({ fileType: 'jpg', canvasId: 'customCanvas', success: (res) => { console.log(res.tempFilePath) //为canvas的虚拟地址 } }) res: { errMsg: "canvasToTempFilePath:ok", tempFilePath: "http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr….cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg" } [代码] 这里需要把canvas里面的内容,导出成一个临时地址才能保存在相册,比如: http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg 五、询问并获取访问手机本地相册权限 [代码]wx.getSetting({ success(res) { console.log(res) if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限 wx.authorize({ //获取权限 scope: 'scope.writePhotosAlbum', success() { console.log('授权成功') //转化路径 self.saveImg(); } }) } else { self.saveImg(); } } }) [代码] 判断是否有访问相册的权限,如果没有,则请求权限。 六、保存到用户手机本地相册 [代码]wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: function (data) { wx.showToast({ title: '保存到系统相册成功', icon: 'success', duration: 2000 }) }, fail: function (err) { console.log(err); if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") { console.log("当初用户拒绝,再次发起授权") wx.openSetting({ success(settingdata) { console.log(settingdata) if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('获取权限成功,给出再次点击图片保存到相册的提示。') } else { console.log('获取权限失败,给出不给权限就无法正常使用的提示') } } }) } else { wx.showToast({ title: '保存失败', icon: 'none' }); } }, complete(res) { console.log(res); } }) [代码] 保存到本地需要一定的时间,需要加一个loading的状态。 七、关于组件中引用canvas [代码]let ctx = wx.createCanvasContext('posterCanvas',this); //需要加this [代码] 在components中canvas无法选中的问题: 在components自定义组件下,当前组件实例的this,表示在这个自定义组件下查找拥有 canvas-id 的 <canvas> ,如果省略则不在任何自定义组件内查找。
2021-09-13 - 小程序canvas绘制海报
2020年第一篇文章,年初忙着复习刷题一直没空去写东西,书看的越多感觉越技不如人,始终徘徊在小菜鸡的行列中,最近项目里正好有一个canvas的业务,突然又燃起了我一个UI前端的火种,记下了踩坑和思考🤔。 踩坑💥 问题1:为什么在canvas上画图片模糊? 在canvas上绘制图片/文字的时候,我们设定canvas:375*667的宽高,会发现绘制出来的图片很模糊,感觉像是一张分辨率很差的图片,文字看起来也会有叠影。 [图片] 注意:物理像素是指手机屏幕上显示的最小单元,而设备独立像素(逻辑像素)计算机设备中的一个点,css 中设置的像素指的就是该像素。 原因:在前端开发中我们知道一个属性叫[代码]devicePixelRatio(设备像素比)[代码],该属性决定了在渲染界面时会用几个(通常是2个)物理像素来渲染一个设备独立像素。 举个例,一张100*100像素大小的图片,在retina屏幕下,会用2个像素点去渲染图片的一个像素点,相当于图片放大了一倍,因此图片会变得模糊,这也是1px在retina 屏上变粗的原因。 [图片] 解决: 将canvas-width和canvas-height都放大2倍,在通过style将canvas的显示width,height缩小2 倍. 例如: [代码]<canvas width="320" height="180" style="width:160px;height:90px;"></canvas> [代码] 问题2:如何处理px和rpx的转换? rpx是小程序里特有的尺寸单位,可以根据屏幕的宽度进行自适应,而在iphone6/iphonex上,1rpx等于不同的px。所以很可能会导致在不同手机下,你的canvas展示不一致。 在绘制海报的之前,我们拿到的设计稿一般都是基于iphone6的2倍图。而且从上一个问题的解决,我们知道canvas的大小也是2倍的,所以我们可以直接量取2倍图的设计稿直接绘制canvas,而尺寸需要注意一下rpxtoPx. [代码]/** * * @param {*} rpx * @param {*} int //是否变成整数 factor => 0.5 //iphone6 pixelRatio => 2 像素比 */ toPx(rpx, int) { if (int) { return parseInt(rpx * this.factor * this.pixelRatio) } return rpx * this.factor * this.pixelRatio } [代码] 问题3:关于canvasContext.measureText计算纯数字的时候手机上为0 在小程序中提供[代码]this.ctx.measureText(text).width[代码]去计算文本的长度,但是如果你全[代码]数字[代码] 的话,你会发现该API永远都计算成0.所以,最后采用模拟measureText方法去计算文本长度。 [代码]measureText(text, fontSize = 10) { text = String(text) text = text.split('') let width = 0 text.forEach(function(item) { if (/[a-zA-Z]/.test(item)) { width += 7 } else if (/[0-9]/.test(item)) { width += 5.5 } else if (/\./.test(item)) { width += 2.7 } else if (/-/.test(item)) { width += 3.25 } else if (/[\u4e00-\u9fa5]/.test(item)) { // 中文匹配 width += 10 } else if (/\(|\)/.test(item)) { width += 3.73 } else if (/\s/.test(item)) { width += 2.5 } else if (/%/.test(item)) { width += 8 } else { width += 10 } }) return width * fontSize / 10 } [代码] 问题4:如何保证一行字体的居中展示?多行呢? 字体的如果过长,会超出canvas画布,造成绘制难看,这个时候我们就应该让超出的部分变成[代码]...[代码] 你可以设置一个width并且循环计算计算出文本的宽度,如果超出则利用substring截取后补充[代码]...[代码]即可。 [代码]let fillText='' let width = 350 for (let i = 0; i <= text.length - 1; i++) { // 将文字转为数组,一行文字一个元素 fillText = fillText + text[i] // 判断截断的位置 if (this.measureText(fillText, this.toPx(fontSize, true)) >= width) { if (line === lineNum) { if (i !== text.length - 1) { fillText = fillText.substring(0, fillText.length - 1) + '...' } } if (line <= lineNum) { textArr.push(fillText) } fillText = '' line++ } else { if (line <= lineNum) { if (i === text.length - 1) { textArr.push(fillText) } } } } [代码] 文字剧中展示计算公式: 居中在canvas中可以用(canvas的宽度-文字宽度)/2 + x (x为字体的x轴的推移) [代码]let w = this.measureText(text, this.toPx(fontSize, true)) this.ctx.fillText(text, this.toPx((this.canvas.width - w) / 2 + x), this.toPx(y + (lineHeight || fontSize) * index)) [代码] 问题5:在小程序中如何处理网络图? 关于在小程序里使用网络图片,比如cdn上的图片,是需要down到微信本地进行 LRU 管理,让后续绘制同样图片时,节省下载时间。所以首先需要你在微信小程序的后台配置downloadFile合法域名,其次你可以在canvas绘制之前,最好提前去down图片,等待图片下载好了,再开始绘制,以避免一些绘制失败的问题。 问题6:在 IDE 中可设置 base64 的图片数据进行绘制,但真机上无用? 先把 base64 转成 [代码]Uint8ClampedArray[代码] 格式。然后再通过 [代码]wx.canvasPutImageData(OBJECT, this)[代码] 绘制到画布上,然后把画布导出为图片。 <!–### 问题6:如何画一个圆角图片?–> 问题7:关于wx.canvasToTempFilePath 使用 Canvas 绘图成功后,直接调用该方法生成图片,在IDE上没有问题,但在真机上会出现生成的图片不完整的情况,可以使用一个setTimeout来解决这个问题。 [代码]this.ctx.draw(false, () => { setTimeout(() => { Taro.canvasToTempFilePath({ canvasId: 'canvasid', success: async(res) => { this.props.onSavePoster(res.tempFilePath)//回调事件 // 清空画布 this.ctx.clearRect(0, 0, canvas_width, canvas_height) }, fail: (err) => { console.log(err) } }, this.$scope) }, time) }) [代码] 问题8:关于canvasContext.font fontsize 不能使用小数 如果设置 font 中字体大小部分包含小数,则会导致整个 font 设置无效。 问题9:安卓下字体渲染错位? [图片] 这个问题出现在安卓手机上,ios表现正常。一开始看到这个问题,摸不着头脑,为什么有的正常居中有的却往前了很多。后面发现是安卓下[代码]this.ctx.setTextAlign(textAlign)[代码] 默认是为center,所以导致了错乱,改成left后就正常了。 问题10:绘制一个折线图 [图片] 利用canvas绘制一个简单的折线图,只需要利用[代码]lineTo[代码]和[代码]moveTo[代码]俩个API将点连接即可。利用[代码]createLinearGradient[代码]绘制阴影。 思考💡 思考1:用json配置表生成海报的局限 现在的海报生成只需要按照设计稿去量取尺寸就可以,但是量取的过程还是很繁琐的,在设计稿量不到的地方还需要手动微调一下。 后续还可以做一个web端使用拖拽的方式去完成设计稿的事情,自动生成json应用到小程序的海报上。 思考2:后端生成海报的局限 海报一开始是后端同学生成的,优点是不需要前端绘制时间,也不需要去踩微信API的坑,接口返回拿到url即可展示,但是在后端生成出来的效果不佳,毕竟这种工作更加前端一些。 思考3:前端生成海报的局限 前端生成海报的时候我发现耗时更长,包括图片的下载本地而且还需要给安卓一个特意写一个setTimeout去确保绘制正常。各种兼容性问题、手机的dpr、安卓和ios等不间断彩蛋踩到你头秃~ 哈哈哈哈~ 彩蛋 采用了最新的canvas-2d背景图确无法绘制全部? 在canvas开发的过程中,小程序里一直有一束微光提醒我。 [图片] 我也试了试最新的canvas2d的api,的确同步了web端,写法也更流畅,在开发者工具中看是一切正常,跑在手机上则,只显示宽度的一半在各种机型下测试也是一样。 [图片] 后面改成原始的canvas就又好了。。。具体原因也还没有在微信社区里找到,后续迭代升级的时候再研究阿吧啊吧啊吧。 [图片]
2020-07-09 - 微信小程序map组件怎么实现带洞多边形?
如图: [图片]
2022-10-29 - 地图组件行政区块高亮,其他位置有遮罩层,怎么实现?
[图片]
2023-09-07 - 不同页面通过订阅消息事件的方式互相通信
业务背景是tabbar有一个新增的功能,在不同的页面都可以操作新增,保存后首页以及个人中心页面的清单数据都要马上更新,但又不想通过调用接口的方式更新。因此自己搞了一个简单的消息事件订阅,比如A,B,C三个页面同时订阅更新事件,那么在D页面操作了更新动作后就会通知A,B,C三个页面。 代码示例: event.js //事件集合 const eventInfo = {}; /** * 注册消费者 * 相同事件、相同消费者名称时会被覆盖 * @param eventName 事件名称 * @param callback 回调事件 */ const on = (eventName, callback) => { const pages = getCurrentPages(); const consumerName = pages[pages.length - 1].route; if (!eventInfo[eventName]) { console.warn("广播事件不存在,不处理", eventName, consumerName); return; } const consumerInfo = eventInfo[eventName]; consumerInfo[consumerName] = callback; console.log("注册消费者完毕=>", eventName, consumerName); } /** * 取消消费者 * @param eventName 事件名称 */ const off = (eventName) => { const pages = getCurrentPages(); const consumerName = pages[pages.length - 1].route; if (!eventInfo[eventName]) { console.warn("广播事件不存在,不处理=>", eventName, consumerName); return; } const consumerInfo = eventInfo[eventName]; delete consumerInfo[consumerName]; console.log("取消消费者完毕=>", eventName, consumerName); } /** * 推送事件消息 * @param eventName 事件名称 * @param data 消息体 */ const emit = (eventName, data = {}) => { if (!eventInfo[eventName]) { console.warn("广播事件不存在,不处理=>", eventName, data); return; } const consumerInfo = eventInfo[eventName]; const consumerNames = Object.keys(consumerInfo); consumerNames.forEach(consumerName => { if (typeof consumerInfo[consumerName] == 'function') { console.log("推送事件消息完毕=>", eventName, consumerName, data); consumerInfo[consumerName](data); } else { console.warn("消费者没有回调函数,不处理=>", eventName, consumerName, data); } }) } /** * 批量推送事件消息 * @param eventNames * @param data */ const emitBatch = (eventNames, data) => { eventNames.forEach(eventName => { emit(eventName, data); }) } /** * 初始化 */ const init = () => { try { console.log("初始化广播通道"); const eventNames = Object.keys(EVEN_NAME); eventNames.forEach(eventName => { eventInfo[eventName] = []; }) } catch (e) { console.error("初始化广播通道出现异常:", e) } } /** * 事件列表 * @type {{REFRESH_CARD_LIST: string, REFRESH_USER_INFO: string}} */ const EVEN_NAME = { RELOAD_CARD_LIST: "RELOAD_CARD_LIST",//重新加载清单数据 RELOAD_USER_INFO: "RELOAD_USER_INFO",//重新加载用户信息 SAVE_CARD_LIST: "SAVE_CARD_LIST",//保存清单 JOIN_INFO_FINISH: "JOIN_INFO_FINISH",//共享完成 JOIN_INFO_FINISH_CONFIRM: "JOIN_INFO_FINISH_CONFIRM",//共享完成确认 } module.exports = {init, on, off, emit, emitBatch, EVEN_NAME} 订阅消息: event.on(event.EVEN_NAME.RELOAD_CARD_LIST, data => { console.log("接收到刷新清单事件推送", data); this.loadData(); }); event.on(event.EVEN_NAME.RELOAD_USER_INFO, data => { console.log("接收到刷新用户信息事件推送", data); auth.setUserInfo2Data(app, this).then(); }); event.on(event.EVEN_NAME.JOIN_INFO_FINISH, data => { console.log("接收到共享记录完成事件推送", data); this.loadData(); auth.setUserInfo2Data(app, this).then(); this.toast.showToast({text: "清单共享成功啦"}); }) 发送消息: event.emit(event.EVEN_NAME.SAVE_CARD_LIST, e);
02-07 - [省钱小妙招]当业务起来,”云开发“费用起飞后如何省钱!
[图片] 很多小程序的早期开发者,为了快速起项目,会使用微信的云函数、云开发来作为后台的数据存储和交互,给我们提供非常多便利的同时,也需要关注一些问题: [图片] 假如这个项目火了!数据量一下子大上去后,套餐用完直接费用炸了~ 除了把后端迁移到xx云服务器外,想继续使用云开发的话,有什么好的控制成本的方案呢? 一、业务起来,云开发费用炸了 下面来看下我们遇到的问题: [图片] 1.1 超出套餐后,费用很贵 这里可以看到,套餐最高的级别是999的那个,我们其中一个小程序已经购买了最贵的套餐,现在就遇到这个问题,超出套餐的部分价格会变得很贵。 [图片] 1.2 云开发收费规则分析 [图片] 注意,这里要仔细研究下这个计费模式,你会发现核心是内存占用 云函数并发数:云函数的并发数量是指在任意指定时间对函数代码的执行数量。对于当前的 SCF 函数来说,每个发布的事件请求就会执行一次。因此,这些触发器发布的事件数(即请求量)会影响函数的并发数。 每秒请求量 x 函数执行时间(按秒) 例如,考虑一个处理存储事件的函数,假定函数平均用时0.2秒(即200毫秒),存储每秒发布300个请求至函数。这样将同时生产 300 * 0.2 = 60 个函数实例。 数据库同时连接数 :数据库请求并发数量,如同时有三十个数据库操作请求,则有二十个会同时执行,剩下十个返回超出并发错误;一次数据库请求(无论小程序端发起还是云函数端发起)将耗费一个连接;每个云环境分别有一个同时连接数限制、独立计数。 常驻云函数闲置量:计算公式: 闲置的常驻云函数数量 * 该云函数的配置内存 * 闲置时长 * 常驻云函数闲置量定价 假如数据库查询平均耗时 10ms,那么一个连接可以支持 100qps(1000ms/10ms=100),20个连接可以支持到 2000qps。 二、优化方案: 2.1 云函数操作优化 [图片] 一些需要增删改查的方案,尽可能放在一个云函数里实现,因为如果你修改后,再调用另外一个云函数查找结果,这样计算的时候,就算2个流量~ 2.2 云函数内存调整 如果你用的云函数没有非常复杂的功能,考虑到云函数费用计算的公式。 [图片] 可以把它的内存占用调整到最低档128MB,相比于默认的256MB,每次访问都能省一半内存,效果立竿见影,费用可以节省40%左右~ 2.3 数据静态化 如果你用到云数据库,这里就会有一个问题,你可以一个操作需要调用云函数的同时,还需要使用数据库的资源,一旦超过套餐费用可不低哦,所以你需要做的核心是 尽可能减少对云开发和运数据的使用,我们可以采用以下的优化方案: 如果你的数据是JSON或者不经修改的配置数据,可以使用云存储,这个费用低多了 如果你有一些静态页面,授权文件,或者静态数据,可以使用云主页 [图片] [图片] [图片] 三、官方费用计算网站 https://cloud.weixin.qq.com/cloudbase/price https://developers.weixin.qq.com/miniprogram/dev/wxcloud/billing/price.html 总结: 以上是针对你还是想 继续用微信云开发 的费用优化,对于早期开发项目的小伙伴,本人还是非常推荐使用的,毕竟开发成本是真的低,后期数据和流量上来了,再优化也来得及~
2023-11-30 - 「公众号运营」之自定义菜单篇
公众号运营小知识之自定义菜单篇 一、公众号资料页中没有展示菜单? 前段时间,我发现所有菜单都会显示在公众号资料页上面了。 如果自定义菜单内容设置的消息类型是 “发送消息” ,在公众号的资料页【服务】栏目中不会展示此菜单; 若菜单设置的是 “跳转网页” 、 “跳转小程序” ,则会在公众号资料页【服务】栏目中展示。 [图片] 二、菜单可以跳转到外部链接吗? 未认证订阅号不支持跳转其他网址,只能输入与公众号相关的链接,例如:页面模板、合集、公众号主页、群发/发布的消息; 服务号和已认证的订阅号可以在【跳转网页】中输入外链地址进行跳转。 虽然输入框这里显示着「公众号链接」,但是符合条件的帐号是可以填写外部链接并保存的。 [图片] 三、如何设置纯文字消息类型? 服务号和已认证订阅号可以设置纯文字类型,消息类型选择【发送消息】,菜单内容中选择【文字】; 未认证订阅号不支持设置纯文字类型。 [图片] 四、如何设置多篇文章? 有两种方式可以实现。 1、发送消息 可以在编辑消息的时候,点击「新建消息」,这里最多可以有8篇文章,编辑好后发布。 [图片] 再前往自定义菜单中选择消息类型为【发送消息】,菜单内容选择【图文消息】, [图片] 在上方选择「卡片视图」,选择刚刚刚才编辑好的内容就可以了。 [图片] 2、跳转网页 可以将文章整理成【合集】或【页面模板】,这样就会生成一个链接,打开就是添加好的文章了。 [图片] 合集 每个合集下最多可以添加1000个文章。 [图片] 页面模板 (1)列表模板最多可以放30篇文章; (2)综合模板上半部分封面那里最多添加3篇文章,下半部分最多可以添加5个分类,每个分类下最多可以添加30篇文章。 [图片] [图片] 在消息类型中选择【跳转网页】,在输入框中输入 合集 或 页面模板 的链接即可。 [图片] 五、当前菜单配置已失效并停用? 1、服务器配置 当你进入到公众号后台自定义菜单页面看到 “未开启自定义菜单:由于开发者通过接口修改了菜单配置,当前菜单配置已失效并停用。你可以前往开发者中心进行停用。” 这个提示。 [图片] 这是因为该公众号启用了服务器配置,公众号后台的菜单就会失效并停用。 [图片] 如果需要修改自定义菜单,可以联系开发人员使用API修改:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html 。 如果你们想在公众号后台修改自定义菜单,需要将服务器配置停用。这里要注意:服务器配置停用后可能会影响你们之前通过API设置的一些功能,比如通过API设置的自动回复、通过API设置的自定义菜单等等。在操作之前建议与你们公众号开发人员确认下,确实不需要服务器配置再选择停用。 2、API版本菜单使用中 有一种情况就是显示「API版本菜单使用中」。 [图片] 这是通过API设置了自定义菜单,公众平台后台这里的菜单就失效了,可联系开发人员查看:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Querying_Custom_Menus.html 。 如需删除API版本菜单可联系开发人员调用接口:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Deleting_Custom-Defined_Menu.html 。 六、手机端没同步最新的菜单? 可以先回忆一下当时编辑好菜单内容后是否有点击「保存并发布」按钮? 确定已经点击了,你可以取消重新关注公众号试试或者过一段时间再看看;要是还没有同步显示,可以在公众号后台重新编辑下菜单再点击一下「保存并发布」试试。 一般来说,自定义菜单发布后会在24小时内同步到手机端。 七、无法跳转到小程序指定路径? 如果你在自定义菜单中设置跳转小程序,在手机上无法打开,提示页面路径不存在的问题,有可能是以下原因导致的: 1、填写的路径带有「.html」后缀(像下图这样的),如有去除后保存并发布试试(菜单发布需要一定时间同步,可以参考上面第六条回答。 [图片] 2、填写的路径有误,或者没有在线上版本发布生效。 如何获取小程序页面路径? (一)联系小程序开发者 建议联系小程序开发者获取页面路径,这样会更准确。 (二)开发者工具 开发者可以直接在开发者工具上获取到页面路径。打开「模拟器」,在左下角会有个「页面路径」栏目,复制即可。 [图片] (三)通过公众号获取 随便进入到任一图文消息编辑页面,点击上方菜单栏中的「小程序」(如果没有可以到旁边「…」看下是否被折叠了), [图片] 新版本公众号后台点击下「去搜索」。 [图片] 输入要搜索的小程序名称/AppID/帐号原始ID,进入到下一步。 [图片] 输入框这里默认填写的是小程序的首页路径,需要获取其他的页面路径,可以点击页面上「获取更多页面路径」,根据右侧页面上的提示操作。 [图片] ⚠️要点击的是「复制页面路径」,不要错点成「复制链接」了。 [图片] 复制出来的页面路径形如 pages/index/index 。 需要注意的是,通过这个方法复制出来的路径会带有「.html」,记得要删掉哈。 问题反馈 在使用的过程中如果遇到一些问题,在社区搜索查阅后仍无法解决,你可以前往「社区公众号版块」发帖反馈。 在发帖时请详细说明情况,最好能够提供相关问题截图、附上公众号的原始ID、最后操作的一次时间点(具体到分钟),以便社区官方专员看到帖子后更好地帮你查看问题。
01-15 - 省钱有道之 云开发环境共享小结
#前言 最近为了节省一点小程序的运营成本,一些没啥流量的小程序如果每个月也要19块略微有些肉疼(主要还是穷),研究了一下云环境共享,在这里简单做一下总结。 [图片] 这里有官方的小程序环境共享文档需提前了解一下,具体共享步骤按官方文档操作即可。 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/resource-sharing/introduce.html #注意点 共享环境有几个注意点大致如下: 1、必须是相同主体 2、开通了云开发环境的小程序可以共享给同主体的小程序、公众号,被共享方无需开通云开发环境 3、一个云开发环境最多可以共享给10个小程序/公众号 4、共享后双发均可主动解除 5、按官方文档要求,资源方需有云函数cloudbase_auth,测试时发现没有这个云函数其实也能正常运行,可能我验证的场景还不够多 6、云能力初始化的方式不同,资源方按传统的云环境初始化方式即可,也就是 wx.cloud.init({ env: env.activeEnv, traceUser: true }); 而调用方的初始化方式有所不同 const cloud = new wx.cloud.Cloud({ //资源方AppID resourceAppid, //资源方环境ID resourceEnv, }) // 跨账号调用,必须等待 init 完成 // init 过程中,资源方小程序对应环境下的 cloudbase_auth 函数会被调用,并需返回协议字段(见下)来确认允许访问、并可自定义安全规则 const initRes = await cloud.init(); 后续调用资源方的云函数就用这个cloud就行了:cloud.callFunction({...}); 7、调用方有操作到云存储文件的api也需要用6步骤中的cloud 8、云存储fileId需要用cloud.getTempFileURL转换成临时/永久链接,否则在调用方无法展示 9、一些api的云调用方式也有变化,需指明具体的appid。比如A小程序授权给了B小程序,想给B小程序推送客服消息需要写成 await cloud.openapi({appid:B小程序appid}).customerServiceMessage.send({...}); 10、获取调用方的appid/openid/unionid也有所不同 // 跨账号调用时,由此拿到来源方小程序/公众号 AppID console.log(wxContext.FROM_APPID) // 跨账号调用时,由此拿到来源方小程序/公众号的用户 OpenID console.log(wxContext.FROM_OPENID) // 跨账号调用、且满足 unionid 获取条件时,由此拿到同主体下的用户 UnionID console.log(wxContext.FROM_UNIONID) #适配 基于以上注意点,开始进行适配,由于我是一套代码部署N个小程序,然后一个云环境共享给其他小程序,希望通过配置决定哪个小程序作为资源方,哪些作为调用方 首先是云开发环境的初始化: 1、env.js 环境配置: //云开发环境 const cloudBase = { //使用共享云环境资源,资源方=false,调用方=true useShareResource: false, //资源方AppID resourceAppid: "wx9d2xxxxxxxx0088", //资源方环境ID resourceEnv: "prod-9gxqvi3qb3c257ef", //云环境ID prod: "prod-9gxqvi3qb3c257ef" } 2、api.js 操作模块 const env = require('../env.js'); let cloud; /** * 初始化云能力 * @returns {Promise} */ const wxCloudInit = async function () { const {cloudBase} = env; if (!wx.cloud) { console.error('请使用 2.2.3 或以上的基础库以使用云能力') } else if (cloudBase.useShareResource) { const {resourceAppid, resourceEnv} = cloudBase; // 声明新的 cloud 实例 cloud = new wx.cloud.Cloud({ //资源方AppID resourceAppid, //资源方环境ID resourceEnv, }) // 跨账号调用,必须等待 init 完成 // init 过程中,资源方小程序对应环境下的 cloudbase_auth 函数会被调用,并需返回协议字段(见下)来确认允许访问、并可自定义安全规则 const initRes = await cloud.init(); console.log("初始化云能力完毕:", initRes, "资源方appid:", resourceAppid, "资源方环境ID:", resourceEnv); } else { wx.cloud.init({ env: env.activeEnv, traceUser: true }); console.log("初始化云能力完毕,当前环境:", env.activeEnv); cloud = wx.cloud; } this.cloud = cloud; } /** * 云函数调用 * @param name * @param data * @param success * @param fail * @param complete */ const callCloudFunction = function (name, data, success, fail, complete) { //执行云函数 cloud.callFunction({ // 云函数名称 name: name, // 传给云函数的参数 data: Object.assign({}, data, {env: env.activeEnv}) }).then(res => { typeof success == 'function' && success(res); }).catch(res => { typeof fail == 'function' && fail(res); }).then(res => { typeof complete == 'function' && complete(res); }); }; 3、在app.js中初始化云环境,后续有用到wx.cloud的都需要改成api.cloud const api = require('utils/api.js'); App({ onLaunch: async function (options) { await api.wxCloudInit(); } }); 其次是资源方的获取用户信息调整 每次都要判断wxContext.FROM_OPENID是否为空,不为空则是调用方的用户信息,为空则是资源方的用户信息,略微繁琐,干脆封装了一个npm包wx-server-inherit-sdk,改造了一下getWxContext函数,源码如下,引入这个包后也就可以不用引入官方的wx-server-sdk const cloud = require('wx-server-sdk'); // 保存原始getWXContext方法到另一个变量 const originalGetWXContext = cloud.getWXContext; cloud.getWXContext = function () { //调用原始getWXContext方法 const wxContext = originalGetWXContext.call(this); const {FROM_APPID, FROM_OPENID} = wxContext; //云开发环境共享时获取到的APPID会替换成源方APPID if (FROM_APPID) { Object.assign(wxContext, {APPID: FROM_APPID}); } //云开发环境共享时获取到的OPENID会替换成源方OPENID if (FROM_OPENID) { Object.assign(wxContext, {OPENID: FROM_OPENID}); } return wxContext; } module.exports = cloud; 到此也就大功告成。为了省钱也是够折腾的[哭笑]
2023-08-28 - 违规问题已经申诉?多久能有结果啊?搜索分享功能什么时候可以恢复?
appid: wxb5c95f9139a7459a
2023-06-20 - 小游戏到底怎么画图?
就想根据图片链接,动态展示一张图片,文档真是依托答辩,毫无上下文 [图片] cocos直接用cc.spriteframe抓链接就行了,像这样子: cc.loader.load({url: url, type: 'png'}, function(err,img){ var myIcon = new cc.SpriteFrame(img); self.logo.spriteFrame = myIcon; }) 微信小游戏怎么啥都没有,包括它所谓的游戏引擎,更是毫无详细文档?开发工具也是bug一堆。。
2023-07-24 - 小程序收益,这个收益也太低了吧?
为什么ecpm越来越低,而且1000访客每天只要3快收益
2022-10-25 - 新手个人耗时1个月开发游戏,《粉夏飞行》今天正式上线微信小游戏平台,顺带来聊聊我踩的坑。
个人觉得用Cocos Creator做微信小游戏非常合适,上手很快,会点TS基本扫一般Cocos的用户手册就可以上手了。所以使用Ccc 来写了自己的第一部作品,Cocos的论坛还是挺活跃的,做的过程中遇到问题去Cocos的论坛搜一遍基本都能找到解决方案,安利一下最近用cocos creator刚做的微信小游戏,【粉夏飞行】一款横版飞行游戏,与以往的飞行射击类不同,画风更可爱,在飞行中吃胶囊加快进入BOSS关,然后发射子弹击退恶龙,并获得金币升级战机,游戏轻快休闲,微信扫描二维码就能玩起来,欢迎提bug哈。 [图片] 顺便总结了快速上手Cocos Creator和微信小游戏的学习路径: 一、Cocos Creator篇总结 1、首先熟悉官方的手册和api文档,文档还是比较详细,游戏的方方面面都涉及到了 官方手册: http://docs.cocos.com/creator/manual/zh/ 257 官方api文档: http://docs.cocos.com/creator/api/zh/ 2、快速上手,跟着官方的快速上手项目快速了解一下小游戏的制作基本流程 快速上手项目: http://docs.cocos.com/creator/manual/zh/getting-started/quick-start.html 3、遇到问题了怎么办?先去官方论坛搜一遍,论坛还是比较活跃的,常见的坑都被人踩过了,基本都能找到解决方案 cocos官方论坛: http://forum.cocos.com/c/Creator 4、它山之玉,可以攻玉,看看别人的总结和教程 推荐cocos官方王哲总结的帖子已经相当全面了: http://forum.cocos.com/t/creator/44782 关注cocos的官方微信号,了解最新动态和指导,例如这个总结也不错: https://mp.weixin.qq.com/s/sKCkD2w7B97piZJ1fcRQlQ 5、特效动画相关 5.1、骨骼动画,推荐免费的dragonbones: http://dragonbones.com/cn/index.html dragonbones教程: http://dragonbones.com/cn/learn.html 5.2、shader动画: 推荐这个库: https://github.com/fylz1125/ShaderDemos https://www.shadertoy.com/ 5.3、粒子特效编辑器,推荐免费的: http://www.effecthub.com/particle2dx http://onebyonedesign.com/flash/particleeditor/ 6、物理碰撞系统 box2d物理系统案例推荐这个官方的案例开源项目,里面有很多案例: https://github.com/2youyou2/physics-example 7、开源项目篇 推荐这个游戏项目,做的质量很好,推荐学习: https://github.com/cocos-creator/tutorial-dark-slash 二、微信小游戏篇总结 1、首先也是熟悉cocos发布微信流程,一键发布到微信开放者工具里 http://docs.cocos.com/creator/manual/zh/publish/publish-wechatgame.html 2、微信官方文档和论坛,遇到问题就去扫一遍: https://developers.weixin.qq.com/minigame/dev/index.html 3、微信小游戏的常见关注的几个点: 3.1 、微信游戏圈 给小游戏增加小论坛的入口,让玩家可以分享讨论游戏,还是挺实用的: https://developers.weixin.qq.com/minigame/dev/tutorial/open-ability/game-club.html 3.2、转发小游戏 转发有两个出发地方:1、点击右上角三个点转发小游戏,2、自己在游戏中触发 https://developers.weixin.qq.com/minigame/dev/tutorial/open-ability/share.html 3.3、微信排行榜 关系链数据 排行榜这个比较麻烦一点,微信为了保护自己的关系链数据,引入了游戏的开发数据域的概念,游戏会存在主域和开放数据域两个上下文,在开放数据域里能拿到排行榜这样的关系链数据,然后此时我们将数据信息绘制到sharedCanvas上共享给主域来展示,主域是拿不到关系链数据的 cocos有个官方的解决版本,就是子域也引入cocos的框架来做可能会加大包的大小 这个版本的排行榜不错 https://github.com/864381832/wxGameRank 三、素材资源篇总结 1、图标矢量图等素材 阿里巴巴开源的这个素材库相当不错: http://www.iconfont.cn/search/index 2、音频素材等 http://taira-komori.jpn.org/instrument01tw.html http://www.aigei.com/sound/class/piano/ 3、粒子特效素材等 http://www.effecthub.com/item/demo/Particle2dx/ 安利一下最近用cocos creator刚做的微信小游戏,【粉夏飞行】一款横版飞行游戏,与以往的飞行射击类不同,画风更可爱,在飞行中吃胶囊加快进入BOSS关,然后发射子弹击退恶龙,并获得金币升级战机,游戏轻快休闲,微信扫描二维码就能玩起来! [图片]
2023-02-02 - 云开发使用云存储可以调用内容安全检测图片API吗?
API名称:内容安全检测图片API:openapi.security.imgSecCheck 问题:做的小程序功能是用户发布帖子,涉及文本和图片,文本直接调用云开发的【内容安全】功能,图片期望调用内容安全检测图片API。但是遇到一个问题是,mediaURL需要传HTTP或HTTPS,不能使用云开发中云存储的fileID,只能用外部服务器。 请问,对于云开发的方式,有什么办法可以进行图片的安全检测吗?谢谢! 整改时间快到了,呜呜~
2022-08-24 - 内容安全检测图片API:openapi.security.imgSecCheck完美解决方案。
背景需求: 我个人做了一款小程序的小游戏,本质是小程序。里面有个自定义图片的功能。用户从本地相册选一张图片进行裁剪,之后保存到缓存中或者上传到服务器。然后用户再用这张图片作为素材进行其它操作。这里就涉及到内容安全了,提交审核没有通过也是因为这个没有做内容安全。防止一些色情低俗的事情发生。 正文: 思路:相册选图片 --> 裁剪小的图片 --> 内容安全检测 --> 通过 --> 裁剪大的图片 --> 保存。 失败的原因:绝大多数是因为检测图片不能大于1M,而导致超时,或者是errCode:-1,又或者是其它问题。 [图片] [图片] 核心代码图片: [代码]默认裁剪小尺寸图片 (我的业务需求是正方形图片,也可动态计算宽高比例) [代码] [图片] 检测图片 部分iOS不兼容encoding: ‘ucs2’。注释掉就好了 [图片] [图片] 云函数 [图片] 测试情况: 正常图片不含违法违规,测试20次,全部通过。小程序上线后暂无发现检测失败情况。百度搜索的“人体油画”等等均可通过。 PS:第一次写经验分享哈,看不懂可以问我。体验一下我的小程序想问我这个小程序其它的功能点也可以喔! 技术会迭代更新,用到的技术会有时效性,看编辑时间,可能当时的技术现在不适用了
2020-10-22 - 特约商户发起分账给个人(高比例),是否涉及二清
我公司将食堂承包出去,承包商为个人,用户将钱付款至公司商户中心,公司将80%比例分账给承包商个人,公司拿20%
2022-10-29 - 使用普通商户分账功能,将我们收取的货款中的采购成本实时分账给我们的供货商算是二清吗?
我们公司为零售运营商,零售运营系统为我司自行开发,我司将零售商品收入的成本价格实时分账给我们的供货商,算是二清吗?
2022-11-17 - 关于申请高比例分账
我想问问,关于高比例分账,每增加一个高比例分账客户必须要再申请一次还是申请通过后其它客户也可以使用,是否有更好的替代方案?
2022-01-10 - 批量转账到零钱文档(服务商) 接口疑问
对于这个接口我的理解是:作为服务商,帮助商户批量转账给商户的会员或者粉丝(收款方的唯一标记是openid); 那么第一个问题:服务商怎么知道收款方的openid,是需要专门开发一个小程序或者公众号来获取吗? 第二个问题:这种批量转账的前提是不是需要收款方主动发起一次申请,可以不申请;如果收款用户不主动触发一次,那服务商怎么能够批量获得收款方的openid呢
2021-03-25 - 支持服务商直接调用批量转账到零钱接口,从服务商的微信账户进行转账至个人用户零钱吗?
我司作为服务商开发了一个电商小程序,业务场景是调用分账接口分润后,希望直接从服务商的微信账户进行转账至个人用户零钱,是否能直接调用批量转账到零钱接口?即https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_3_1.shtml 1.以上场景是否能直接调用批量转账到零钱接口? 2.我看文档中需要“特约商户号”,但服务商不是不能作为同一主体的服务商下的“特约商户”吗? 3.如不能通过接口,是否有其他形式实现上面的功能?
2022-09-13 - 列表拖拽排序置顶删除效果
一个简单的拖拽排序置顶效果, 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 一个简单的拖拽排序置顶效果 [图片] https://developers.weixin.qq.com/s/0HZoZUmr7EAQ
2022-07-21 - 小程序搜索优化指南(SEO)
2019年上半年微信发布了基于小程序页面的搜索,为了让我们更好地发现及理解小程序的页面,结合过去一段时间来我们遇到的各种情况,我们强烈建议各位开发者花一些宝贵的时间认真阅读本文:) 爬虫访问小程序内页面时,会携带特定的 user-agent "mpcrawler" 及场景值:1129 1. 小程序里跳转的页面 (url) 可被直接打开。 小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。特别的:建议页面所需的参数都包含在url 2. 页面跳转优先采用navigator组件。 小程序提供了两种页面路由方式: a.navigator 组件 b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch 建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。 3.清晰简洁的页面参数。 结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。 建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。 5. 我们不收录 web-view 中的任何内容。 我们暂时做不到这一点,长期来看,我们可能也做不到。 6. 利用 sitemap 配置引导爬虫抓取,同时屏蔽无搜索价值的路径。 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html 7. 设置一个清晰的标题和页面缩略图。 页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。 通过wx.setNavigationBarTitle或 自定义转发内容onShareAppMessage对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster /poster-for-crawler属性。 8. 使用页面路径推送能力 可极大丰富微信可以收录的内容,进而提高小程序内容的曝光机会。请参考: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.submitPages.html
2020-01-14 - 微信小程序长期订阅?
微信小程序怎么申请长期订阅
2022-09-07 - 「代金券」产品介绍
一、简介 商户可通过接入微信支付营销API,获取代金券的全过程解决方案,在商户已有的平台实现制券、发券、查券、修改等功能。同时可享受返佣所获得的收益,及微信支付代金券的特有能力和优势。 ● 能力:微信场景内投放、安全防刷机制,品牌曝光 ● 优势:高触达性、高服务连接能力 二、功能介绍 ■ 功能强大,满足大多数营销需求 微信支付代金券支持全场满减券、单品满减券、单品换购券,满足大多数日常营销需求。 ■ 流程完善,复杂逻辑全搞定 营销活动除了基础的制券、发券,还包含使用优惠计算、退款、对账等复杂的流程。微信支付提供了完整的方案,可供商户/服务商使用。 ■ 场景灵活,自定义场景发券 商户可以在任意场景(例如:小程序、H5、APP等)调用API发券,也可以定义活动形式(例如:全员发券、新用户发券、抽奖发券等)。 ■ 更好的触达 可插入卡包,在券过期前,用户会收到过期提醒,提升核销转化率。 ■ 安全防刷机制 结合微信强大的安全防刷能力,阻止黑产的不法行为,为营销资金保驾护航。 三、API相关限制 1、可调用接口的角色:每个接口的限制不同,可参考每个接口的规则。 2、调用时mchid和APPID需要有绑定关系,可在【微信支付商户平台—>产品中心—>开发配置】进行绑定,APPID和openid需要有对应关系。 四、操作指引 1、创建代金券批次 通过创建代金券批次接口,或登录微信支付商户平台创建代金券批次(操作路径:【微信支付商户平台—>营销中心—>创建全场/单品代金券】)。 可创建代金券的类型包含预充值和免充值两种类型。 ● 预充值代金券适用于第三方出资策划的活动,例如:满100减10. 指订单金额100元,用户实付90元,商户实收100元。 ● 免充值适用于商户策划的活动,例如:满100减10。 指订单金额100元,用户实付90元(用户领券后,在支付中直接核销10元),商户实收90元。 2、激活代金券批次 制券成功后,通过调用激活接口激活代金券批次。 说明:如果是预充值代金券,激活时从商户账户余额中锁定本批次的营销资金。 3、发放代金券 通过商户平台/API完成制券后,调用发放代金券接口进行代金券发放。 4、支付中自动核销券 用户领券后,在支付过程中自动核销券中所示金额,实付金额=订单总金额-代金券金额。 当一个券被核销后,微信支付侧会推送通知给商户/服务商服务器(前提是商户已设置接收通知的URL地址)。通知信息包括券ID、核销信息、单品信息等。 5、管理代金券 可通过查询、修改相关接口进行代金券管理。 6、营销事件推送 当营销相关事件发生时,向商户配置的服务器地址发送通知。目前支持核销通知。商家需要设置接收通知的URL地址,并在商户平台开通营销事件推送的能力,即可接收到相关通知。具体内容参见《核销事件回调通知》。 说明:如果不能收到推送信息,请按照【准备工作】自查,重点检查【API密钥设置】。 可点击查看制券环节的课程视频↓↓↓ [视频]
2021-10-12 - 「干货分享」一文了解微信优惠券产品(卡券、代金券、商家券)
相信很多产品运营和开发的朋友刚接触到微信营销,听到什么优惠券、卡券、代金券、支付券、商家券,是不是一脸懵逼,我只是想做个优惠而已,要不要这么复杂,这到底该接哪一个?希望这篇文章能让你有一个更加清晰的了解。 不管是卡券、代金券、商家券这些我们都可以统称为“优惠券”,而微信支付代金券有另一种叫法“支付券”,其实支付券还包含立减折扣的。 [图片] 优惠券定义 卡券:是微信公众号提供的一套电子卡券解决方案,实现卡券生成、下发、领取、核销的闭环,并使用对账、卡券管理等配套功能。 微信卡券能力不只包含普通的优惠券(代金券、折扣、兑换、团购、优惠券),还有会员卡、礼品卡、票证(电影票、汽车票、景点门票等)。商户可自行在公众平台或通过 接口 创建卡券,多种渠道投放给用户,用户用券时需核销卡券。 比如100元的订单金额,用户有一张10元代金券,商家先核销这10元代金券,再计算用户实际需要支付金额(90元),支付方式不限制微信支付、其他支付也是可以的。 代金券(即支付券):是微信支付面向商户的一种营销工具,商户创建代金券,可以发送给用户,当用户使用微信支付时,代金券会伴随交易自动核销/抵扣,帮助商户便捷地落地营销活动。 代金券类型包含预充值和免充值两种类型,预充值代金券适用于第三方出资策划的活动,例如:满100减10. 指订单金额100元,用户实付90元,商户实收100元;免充值适用于商户策划的活动,例如:满100减10。 指订单金额100元,用户实付90元(用户领券后,在支付中直接核销10元),商户实收90元。 [图片] 商家券:是微信支付为商户提供的电子优惠券解决方案,商家可在微信支付允许的范围内通过该功能实现商家优惠券信息生成、下发、领取、核销的闭环,并使用数据对账、券信息查询等配套功能完成商家券的管理操作。(目前只提供API接口功能,暂无法在商户平台创建) 其实可以说商家券就是卡券优惠券的升级版,都是商家自主核销,只是他们分属不同的平台,一个是公众号(卡券),一个是微信支付商户平台(商家券)。 [图片] 重要通知 微信卡券-优惠券功能现即将下线,有发券需要的商户尽快升级到“微信支付优惠券”:商家券或支付券(即代金券)。此次模块升级不涉及会员卡、礼品卡、票证产品不影响。https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11614329634x9Pvw&version=&lang=zh_CN&token= 优惠券产品框架 从投放场景、类型、核销看一下优惠券生态圈 [图片] 产品能力对比 内容 卡券(即将下线) 支付券(代金券) 商家券 平台体系 微信公众号 微信支付商户号 微信支付商户号 核销规则 商家核销,不限制微信支付 微信支付自动核销 商家核销,不限制微信支付 卡包 领券后进入卡包 平台发券,自动进入卡包;API发券需申请插卡权限 领券后进入卡包 自定义券码 支持 不支持 支持 营销场景 二维码;公众号消息;朋友圈广告;商家H5/APP/小程序 二维码;朋友圈;商家H5/APP/小程序;平台扫码领券、支付有礼、附近发券等 二维码;朋友圈;商家H5/APP/小程序;平台扫码领券、支付有礼、附近发券等 营销经费 无需充值(垫资) 支持预充值和免充值 无需充值(垫资) 开发能力 后台支持基本创建券与核销,同时支持商家API接口 后台支持创建券发券场景,同时支持API接口 目前只有API接口,无法在后台创建券 注:卡券优惠券产品即将下线,就不要过多关注了,了解一下就好了哈~~ 相关链接 微信卡券产品文档:https://mp.weixin.qq.com/cgi-bin/readtemplate?t=cardticket/faq_tmpl&type=info&token=1472580499&lang=zh_CN 微信卡券接口文档:https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/WeChat_Coupon_Interface.html 微信支付代金券产品文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter5_1_1.shtml 微信支付商家券产品文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter5_2_1.shtml
2021-05-10 - 6.1 儿童节 CocosCreator 超级大礼包!
今天是 6.1 儿童节,在此祝大小朋友们节日快乐!!!并献上社区十多位开发者的知识大礼包! 还记得我们在今年4月时推出的 《Creator H5全平台游戏开发教程 PDF 免费下载(800+页)》吗? [图片] image 当时晓衡就计划好了,每隔一段时间,会将公众号的上的干货精华进行整理,方便大家搜索查阅。2020 年上半年的 PDF 也逐渐有了雏形,感谢:Jare、白玉无冰、异名、李逍遥、陈皮皮、KUOKUO、肖尧、大大大火球、IT老兵哥、13yd、zilone,也感谢我自己! 0. 开发者自己的故事见证开发者的成长历程,向他们致敬Jare | 讲述开发者自己的故事— 引擎大神白玉无冰| 讲述开发者自己的故事—转攻难点异名 | 讲述开发者自己的故事—原创沉淀肖尧 | 讲述开发者自己的故事—物理专攻李逍遥 | 讲述开发者自己的故事—武士青年陈皮皮 | 讲述开发者自己的故事—骨瘦如柴1. Creator零基础视频教程序:Creator零基础视频教程,为抗疫尽一份力量!1-1 游戏引擎编辑器-介绍1-2 游戏引擎编辑器-核心面板2-1 内置组件介绍-组件分类与创建2-2 内置组件介绍-精灵Sprite2-3 内置组件介绍-文字标签Label2-4 内置组件介绍-按钮Button3-1 节点与组件-节点的能力3-2 节点与组件-组件为节点赋予能4-1 游戏场景的创建与编辑4-2 编写通用场景切换组件2. Cocos Shader游戏开发者心中的梦中情人渴望学习 Shader的伙伴有福了!高斯模糊详解!毫无PS的图片,依然这么炫酷!Shader 滤镜来了!详解圆形头像Shader颜色可变的剪影效果 Shader详解 Creator 追光效果,Shader 实现!!!Shader入门实战,拳皇被击闪白特效!给你看看用Shader做的菊花!漂不漂亮新版 ShaderHelper 使用教程翻书、戳牌、波纹、高光 Shader 组件工具超动感音乐可视化:WebAudio与Shader的震撼结合!研究了3天,终于将 Shader 移植到 Cocos Creator 2.2.0 上了ShaderHelper2正式回归,并全面使用TypeScript语言!Shader挖洞,不要太惊讶!3. 实战案例(源码)只讲理论不讲实战都是在耍流氓手写遥控杆实现(附源码)刷爆技术圈的贪吃蛇大作战,教程讲解,附Github源码大家要的瞄准线教程找到了,附github源码!Creator3D 图文教程【打砖块】终于撸出来了,附送最新源码!小工新作《动物同化》提供完整源码!Creator 国旗头像生成器,源码奉上!口罩头像定制,一起为抗击疫情尽一份力量!附源码!用一次函数来实现瞄准线的反射小游戏激励视频组件,免费开源!开源的自动代码混淆插件消消乐类游戏!链式消除与自动补齐!Cocos Creator不规则拼图游戏!使用精灵网格Mesh渲染模式 !新手入门太难了,好友排行榜!手把手教你!月亮围绕着地球旋转,在Creator中如何实现?放大镜效果背景无限滚动!皮皮的多分辨率适配方案金币落袋动画的实现异名程序员的刮刮卡教程使用 Cocos 进行 2D 和 3D 混合开发异名的超全面 Cocos Creator 入门教程!物体随机飞溅运动4. 物理专题价值千万的技术揭秘物理挖洞、鳄鱼洗澡、百战天虫!白玉无冰专业物理挖坑!优化篇!生命不歇,挖坑不止!另一种挖洞算法的实现!挖下的坑,刨出来的是金砖 !物理刚体挖洞新方案Cocos Creator 物理流体!惊呆了...Cocos Creator之物理切割5. 视频教程不仅会写,也要学会演【捕鱼来了】1. 浏览器实战演示【捕鱼来了】2. 字体打包工具【捕鱼来了】3. 皮肤预览【捕鱼来了】4. 鱼字群制作【捕鱼来了】5. 更换渔场背景【捕鱼来了】6. 鱼的命名与换皮【捕鱼来了】7. 图集整合与鱼的图集分配【球球要回家】1-1 工程资源结构【球球要回家】1-2 小球曲线运动【球球要回家】3-1 棋盘布局与路径求解【球球要回家】3-2 一个变量名字引发的卡死BUG【球球要回家】3-3 小球路曲线运动实现细节【球球要回家】4-1 微信登录组件【球球要回家】4-2 初始化微信云【球球要回家】4-3 使用微信云获取openid【球球要回家】4-4 微信授权获取用户信息【源码精讲】零编程激励视频组件【源码精讲】图集与纹理压缩【源码精讲】实战原生 JSB 启动闪退的调试技巧【源码精讲】版 ShaderHelper 使用教程6. 语言&IDE全方位精进,提升工作效率为什么Creator 3.0将仅选择使用 TypeScript ?为什么要选择使用TypeScript,看了就知道原因!开源新手引导框架,全面支持TypeScriptTypeScript贪吃蛇大作战,教程讲解链表的应用—贪吃蛇游戏(附项目TypeScript)按我说的来,让 VS Code 更好用10倍 | VS Code 新手指南VS Code 1.40 发布!可自行搭建 Web 版 VS Code!重磅!微软发布 Web 版 VS Code + 云开发环境VScode 的 JS 智能提示弱爆了?但是我有办法!10个很实用的 JavaScript 技巧JavaScript 中的三位一体JavaScript ES6—Map的妙用了不起的 Deno 入门教程!五分钟了解浏览器工作原理盘点游戏中那些“欺骗玩家眼睛的开发技巧”游戏开发中用到的MVVM设计模式大火球的设计模式-启蒙篇妞太多,如何管理?大话设计模式!用一个变量切换不同渠道,SDK接入整合Creator 引擎资源引用查找插件,这个问题终于有救了!7. Cocos Creator 3D3D 是未来,未来!虚拟才是现实怎么获取3D素材,进行 Cocos Creator 3D 游戏开发体验!Creator 3D v1.0.1 测试版发布,提供最新案例在线体验!Creator 3D 新教程,你能射中靶心么?Creator 3D 入门实战,蚂蚁庄园运动会星星球!Cocos Creator 3D 物理模块介绍Creator 3D 实现小姐姐高光发丝,这帮引擎大佬真的是服了!手把手教你起步Creator 3D横版酷跑游戏你还在观望吗?Creator3D小鲜肉《小鸭快跑》新鲜出炉!在Creator 3D中使用Protobuf的快速解决方法!使用Creator 3D 制作的打砖块,附源代码!Creator 3D打砖块DEMO升级,终于可在手机上玩了!Creator 3D 图文教程【打砖块】终于撸出来了,附送最新源码!Creator3D案例新作,守护你的球球Creator 3D 官方惊艳的DEMO,附在线体验!如何在 Creator 3D 中切换模型贴图,超级简单Creator3D 打砖块子弹发射,以及摄像机平滑移动控制!8. 副业&职涯一起探索游戏开发挣钱之道试玩、源码、教程,助力游戏开发者副业之路!副业挣钱之小游戏开发最新模式揭秘不老程序员修练指南(1)—常见问题&分享大纲不老程序员修练指南(2)—揭秘程序员青春饭的机理不老程序员修练指南(3)—发现属于自己的神山哪种岗位更有前(钱)途?唉!痴迷于奶头乐,为什么我才坚持几分钟就...?程序员装逼指南(精华)质量装B大会,噢,说错了,是质量回溯会!软件Bug太多是咋会事?听大火球给你讲清楚其中原因!怎样开发每天赚100万的微信小游戏?怎么用开发技术赚外快?不写情书,程序员还要学写作吗?如何跨越自我推销的难关?两年半,「副业增收」终于不再是一句口号了!(真实案例)9. 晓衡的冒险成长工程师经纪人的修练日志一个玩游戏的失足青年,转行做软件开发的挣扎过程(1)一个玩游戏的失足青年,转行做软件开发的挣扎过程(2)一个玩游戏的失足青年,转行做游戏开发的挣扎过程(3)有个事正在悄然发生,估计谁都无法逆转!只能积极拥抱漫画,如何给一年级小学生讲游戏开发的!(真实案例)程序员,如何才能越老越吃香?落地指南见自己,见天地,见众生!登山做个灯泡!小游戏开发运营模型之—游戏调优篇(1)香港马市、田忌赛马?这款游戏 IP 碉堡了软著有什么用呢,游戏开发需要注意哪些法律问题?香港马市、田忌赛马?这款游戏 IP 碉堡了成语小状元被吐槽了,但又被我们神逆转!免费升级!何为民间IP,小游戏竟然还可以这样做?脑洞大开Creator星球最新黑科技,游戏开发不用拖拉!一起学习,来当个流量主吧!小游戏运营思路解析人类,未来真的会成为 AI 圈养的宠物吗?我的奋斗目标,不写代码,照样可以做游戏大龄个人开发者,我是如何活下来的,又将怎样活下去24小时极限开发,加速助力开发者副业变现这位程序猿做了全世界程序猿都想做但又害怕做的事情!这么大的一份礼包,感觉一天的假期不够,将6.1儿童儿改为6月儿童改有多好呀!愿我们一起成长,每年都能一起过儿童节! 童心、同心、同行!
2020-06-01 - 小程序自定义顶部导航栏实现相关总结
本文背景最近在小程序迭代过程中,打算引入自定义导航组件,这在之前是一个未接触的知识点,对我而言也是一种挑战 [图片] 本文内容本文主要列出我在实现过程中参考社区以及社区之外的文章 1)胶囊的布局位置及尺寸信息 https://developers.weixin.qq.com/miniprogram/dev/api/ui/menu/wx.getMenuButtonBoundingClientRect.html 2)状态栏高度信息 https://developers.weixin.qq.com/miniprogram/dev/api/base/system/system-info/wx.getSystemInfo.html 3)https://developers.weixin.qq.com/miniprogram/design/ 4)小程序组件—自定义顶部导航 https://juejin.im/post/6844903862852141070 5)微信小程序自定义导航栏组件 https://juejin.im/post/6844903860029358094 6)如何实现一个自定义导航栏? - https://developers.weixin.qq.com/community/develop/article/doc/000060f9cf8900246c789a36453413 7)小程序自定义导航栏的开发原理及编码思路实践 (附带可直接使用的自定义导航栏组件)? https://developers.weixin.qq.com/community/develop/article/doc/000646ab68003095767aaa15b5b013 实现效果最终顶部导航栏实现的效果就是如下图所示 效果一,非全屏 [图片] 效果二,全屏 [图片] 代码片段本代码片段参考社区大佬,仙森,在文章的评论区 https://developers.weixin.qq.com/s/m16krgmD78lE 本文总结关于这块代码就不贴了,在上面的参考文章中都有对应的代码。
2020-10-21 - 小程序粘性布局组件实现
一、前言 开发中,我们经常会遇需要让组件在屏幕范围内时,按照正常布局排列,而组件滚出屏幕范围时,让其始终固定在屏幕顶部的情况,也就是常说的粘性布局。今天我们就一起用小程序来实现一个适用于不同场景下的粘性布局组件。 二、demo演示 如图,实现的组件主要适用于以下几种场景: 吸顶页面最上方; 吸顶与页面有固定距离的位置; 在指定容器内吸顶; 嵌套在scroll-view中吸顶。 [图片] 三、代码演示 其中,粘性组件通过<weimob-sticky></weimob-sticky>调用,参数信息用法如下: 参数 说明 类型 默认值 offset-top 吸顶时与顶部的距离,单位px number 0 z-index 吸顶时的 z-index number 99 container 一个函数,返回容器对应的 NodesRef 节点 function - scroll-top 当前滚动区域的滚动位置,非 null 时会禁用页面滚动事件的监听 number - 滚动时触发scroll函数,其中isFixed为是否吸顶,scrollTop为距离顶部的位置。详细代码如下。 3.1 页面代码 3.1.1 基础用法 [代码]<view class="weimob-block"> <view class="weimob-title">基础用法</view> <view class="weimob-body"> <weimob-sticky> <!-- 需要粘性的部分 --> <button class="margin-left-base" size="mini"> 基础用法 </button> </weimob-sticky> </view> </view> [代码] 3.1.2 吸顶距离 [代码]<view class="weimob-block"> <view class="weimob-title">吸顶距离</view> <view class="weimob-body"> <!-- 吸顶时与顶部的距离,单位px --> <weimob-sticky offset-top="{{ 50 }}"> <!-- 需要粘性的部分 --> <button class="margin-left-top" type="primary" size="mini"> 吸顶距离 </button> </weimob-sticky> </view> </view> [代码] 3.1.3 指定容器 [代码]<view class="weimob-block"> <view class="weimob-title">指定容器</view> <view class="weimob-body"> <!-- 这里需要固定高度 --> <view id="container" style="height: 300rpx;background-color: #fff"> <weimob-sticky container="{{ container }}"> <button size="mini" class="margin-left-special"> 指定容器 </button> </weimob-sticky> </view> </view> </view> [代码] 3.1.4 嵌套在scroll-view使用 [代码]<view class="weimob-block"> <view class="weimob-title">嵌套在 scroll-view 内使用</view> <!-- 这里需要固定高度,scroll-view里的元素高度需要大于其高度 --> <scroll-view bind:scroll="onScroll" scroll-y id="scroller" style="height: 400rpx; background-color: #fff;margin-top: 40rpx;" > <view style="height: 800rpx"> <weimob-sticky scroll-top="{{ scrollTop }}" offset-top="{{ offsetTop }}" > <button size="mini" class="margin-left-scoll"> 嵌套在 scroll-view 内 </button> </weimob-sticky> </view> </scroll-view> </view> [代码] 页面js [代码]Page({ data: { container: null, //一个函数,返回容器对应的 NodesRef 节点 scrollTop: 60, // 当前滚动区域的滚动位置,非null时会禁用页面滚动事件的监听 offsetTop: 0 // 吸顶时与顶部的距离,单位px }, onReady() { // 页面渲染完,获取节点信息 this.setData({ container: () => wx.createSelectorQuery().select('#container'), }); }, onScroll(event) { // 容器滚动时获取节点信息 wx.createSelectorQuery() .select('#scroller') .boundingClientRect((res) => { this.setData({ scrollTop: event.detail.scrollTop, offsetTop: res.top, }); }) .exec(); } }); [代码] 3.2 组件代码 组件wxml [代码]<wxs src="./index.wxs" module="computed" /> <view class="weimob-sticky" style="{{ computed.containerStyle({ fixed, height, zIndex }) }}" > <view class="{{ fixed ? 'weimob-sticky-wrap--fixed' : ''}}" style="{{ computed.wrapStyle({ fixed, offsetTop, transform, zIndex }) }}" > <slot /> </view> </view> [代码] 组件wxs 这里使用使用小程序的wxs对吸顶元素的transform,top,height,z-index元素进行实时渲染,ios设备在滚动监听时性能会优于在js 2-20倍,androd设备效率暂无差异。 [代码]function wrapStyle(data) { var style = ""; if (data.transform) { style += 'transform: translate3d(0, ' + data.transform + 'px, 0);' } if (data.fixed) { style += 'top: ' + data.offsetTop + 'px;' } if (data.zIndex) { style += 'z-index: ' + data.zIndex + ';' } return style; } function containerStyle(data) { var style = ""; if (data.fixed) { style += 'height: ' + data.height + 'px;' } if (data.zIndex) { style += 'z-index: ' + data.zIndex + ';' } return style; } module.exports = { wrapStyle: wrapStyle, containerStyle: containerStyle } [代码] 组件js [代码]import pageScrollMixin from "./page-scroll"; const ROOT_ELEMENT = ".weimob-sticky"; Component({ options: { multipleSlots: true }, properties: { zIndex: { type: Number, value: 99 }, offsetTop: { type: Number, value: 0, observer: "onScroll" }, disabled: { type: Boolean, observer: "onScroll" }, container: { type: null, observer: "onScroll" }, scrollTop: { type: null, observer(val) { this.onScroll({ scrollTop: val }); } } }, data: { height: 0, fixed: false, transform: 0 }, behaviors: [pageScrollMixin(function pageScrollMixinCallback(event) { // 非null时会禁用页面滚动事件的监听 if (this.data.scrollTop != null) { return; } this.onScroll(event); })], lifetimes: { attached() { this.onScroll(); } }, methods: { onScroll({ scrollTop } = {}) { const { container, offsetTop, disabled } = this.data; if (disabled) { this.setDataAfterDiff({ fixed: false, transform: 0 }); return; } this.scrollTop = scrollTop || this.scrollTop; if (typeof container === "function") { // 情况一:指定容器下时,吸顶距离+吸顶元素高度>容器高度+容器距顶部距离,随页面滚动; // 情况二:指定容器下时,吸顶距离>吸顶元素高度,元素固定; // 情况三:元素初始化。 // this.getRect获取节点ROOT_ELEMENT相对于显示区域的top,height等信息,通过root获取 // this.getContainerRect获取父容器相对于显示区域的top,height等信息,通过container获取 Promise.all([this.getRect(ROOT_ELEMENT), this.getContainerRect()]).then( ([root, container]) => { if (offsetTop + root.height > container.height + container.top) { this.setDataAfterDiff({ fixed: false, transform: container.height - root.height }); } else if (offsetTop >= root.top) { this.setDataAfterDiff({ fixed: true, height: root.height, transform: 0 }); } else { this.setDataAfterDiff({ fixed: false, transform: 0 }); } }); return; }else{ this.getRect(ROOT_ELEMENT).then(root => { // 吸顶时与顶部的距离小于可视区域的top距离时,随着滚动条滚动,否则吸顶 if (offsetTop >= root.top) { this.setDataAfterDiff({ fixed: true, height: root.height }); this.transform = 0; } else { this.setDataAfterDiff({ fixed: false }); } return Promise.resolve(); }); } }, setDataAfterDiff(data) { // 比较数据是否与上次相同,不同则触发父组件scroll事件更新isFixed,scrollTop。 wx.nextTick(() => { const diff = Object.keys(data).reduce((prev, key) => { const prevCopy = prev; if (data[key] !== this.data[key]) { prevCopy[key] = data[key]; } return prevCopy; }, {}); this.setData(diff); this.triggerEvent("scroll", { scrollTop: this.scrollTop, isFixed: data.fixed || this.data.fixed }); }); }, getContainerRect() { const nodesRef = this.data.container(); return new Promise(resolve => nodesRef.boundingClientRect(resolve).exec()); }, getRect(selector) { return new Promise(resolve => { wx.createSelectorQuery().in(this).select(selector).boundingClientRect(rect => { resolve(rect); }).exec(); }); } } }); [代码] page-scroll.js 滚动事件在页面进入和离开时共享的pageScrollMixin函数。 [代码]function getCurrentPage() { const pages = getCurrentPages(); return pages[pages.length - 1] || {}; } function onPageScroll(event) { const { weimobPageScroller = [] } = getCurrentPage(); weimobPageScroller.forEach(scroller => { if (typeof scroller === "function" && event) { // @ts-ignore scroller(event); } }); } const pageScrollMixin = scroller => Behavior({ attached() { const page = getCurrentPage(); if (Array.isArray(page.weimobPageScroller)) { page.weimobPageScroller.push(scroller.bind(this)); } else { page.weimobPageScroller = typeof page.onPageScroll === "function" ? [page.onPageScroll.bind(page), scroller.bind(this)] : [scroller.bind(this)]; } page.onPageScroll = onPageScroll; }, detached() { const page = getCurrentPage(); page.weimobPageScroller = (page.weimobPageScroller || []).filter(item => item !== scroller); } }); export default pageScrollMixin; [代码] 总结 最后,我将上述代码放在了代码片段中供大家使用了解,https://developers.weixin.qq.com/s/qiym3wmr7znx ,希望能够帮到小伙伴们,欢迎评论区建议或指教哦~
2021-01-26