- 小程序字体重复加载的优化方案
最近有一个需求,需求方一定要求加载自有的字体,一共5个包,加起来要60M,而且因为有后台上传的文章,还不能用字体精简工具压缩字体。 在调用官方的wx.loadfontface的时候,开始看着一切正常,但真机调试的时候,发现每个页面都在重新下载字体。 先不说每个页面抖动的问题,就是流量也扛不住他这么造啊。 社区里面看到有很多人在反馈这个bug,从20年到23年都有 https://developers.weixin.qq.com/community/develop/doc/0006e6ad5b49b02e3bc98398d51000?highLine=loadfontface%2520%25E7%25BC%2593%25E5%25AD%2598 https://developers.weixin.qq.com/community/develop/doc/00084c852f40280d6b2f6461550000?highLine=loadfontface%2520%25E9%2587%258D%25E5%25A4%258D 本来给客户反馈说微信bug,他们也勉强接受,但是上线10天,跑了1000多rmb的流量钱,实在是顶不住,压力又给到开发这边。。。 后面在掘金看到一篇文章(https://juejin.cn/post/7103928278225780743?searchId=2023081018280858E8AD21B4FDD711555A) 本地路径不行,那把本地文件转成base64曲线救国确实是一条思路。 原文是behavior做的,而且是用云开发,但我们的业务场景是需要全局生效,每个页面都写一个behavior也太累了。 于是改造成以下模式。 先创建一个loadFont.js,代码如下 注释写的比较详细了,简述思路: 先用wx.getFileSystemManager().access()看本地有没有,有就直接load,没有就先下载 wx.downloadFile把字体下到本地,再从本地wx.getFileSystemManager().readFile()取出文件并转Base64 最后source: `url("data:font/woff2;charset=utf-8;base64,${res.data}")`,搞定! // 建议循环调用方法,而不是这个方法内循环下载 // 下载字体文件,注意要把字体域名加到后台downloadFile白名单中 function _downloadFont(fontUrl, filePath, fontFamily) { wx.downloadFile({ url: fontUrl, success: res => { wx.getFileSystemManager().saveFile({ // 下载成功后保存到本地 tempFilePath: res.tempFilePath, filePath, success: res => { // 加载字体 _loadFontFace(fontFamily, res.savedFilePath) } }) } }) } // 加载文件字体转 base64,load function _loadFontFace(fontFamily, filePath) { // 读文件 wx.getFileSystemManager().readFile({ filePath, // 本地文件地址 encoding: 'base64', success: res => { wx.loadFontFace({ global: true, // 是否全局生效 scopes: ['webview', 'native'], //native可能有点问题,超哥生个海报试一下 family: fontFamily, // 字体名称 source: `url("data:font/woff2;charset=utf-8;base64,${res.data}")`, success(res) { console.log(fontFamily + '加载成功:' + res.status) }, fail: function (res) { console.log(fontFamily + '加载失败' + res) }, }) } }) } // fontUrl: 字体地址 // filename: 存储文件路径 // fontFamily: css 中字体的 family function loadCloudFontFace(fontUrl, filename, fontFamily) { const filePath = `${wx.env.USER_DATA_PATH}/${filename}` wx.getFileSystemManager().access({ path: filePath, success: () => { _loadFontFace(fontFamily, filePath) console.log('从本地加载了字体'); }, fail: () => { _downloadFont(fontUrl, filePath, fontFamily) console.log('从外部加载了字体', fontUrl); } }) } module.exports = { loadCloudFontFace } 由于业务本身的需求,是全局所有字体都用,所以,直接在app.js中(实际上我们是抽了一个文件单独维护的,这里就简化吧) const loadFont = require('./loadFont.js'); loadFont.loadCloudFontFace('https://xxx.com/wxapp/fonts/STHeiti.woff2', 'STHeiti.woff2', 'STHeiti') 并且,在app.wxss,让font-family全局给一个默认字体(事实上还有英文字体,这里简化了) page { font-family: 'STHeiti'; } 当然也支持每个页面独立配置不同字体,方法大同小异。 真机测试,终于只加载一次网络字体了。 其他注意的事项: 由于第一次仍然要加载字体,会产生抖动,我们的做法是,尽量不在首页用文字,大量用图片替代,同时在后台加载字体,供其他页面使用。对于个别页面需要加载字体时,可以出个loading框(虽然体验依然不好,但我尽力了)直接load网络字体,会存在跨域的问题,有的时候,这个问题确实很难解决(字体一般都用cdn、对象存储一类的,如果有权限操作配置很简单,但很多情况下开发人员确实不好增加跨域配置)。采用这个方案,由于是本地,就不存在跨域问题了,一举多得。 最后还是要吐槽下,这个需求,并不小众。最早从20年就有bug反馈,官方也有回复,但到23年了这个bug还存在,也着实无奈。
2023-08-10 - 微信小程序发券插件至验签流程
经过两天的穷举尝试,终于把微信小程序发券插件验签流程通过了,在此给大家分享一下。 提过一个问题(https://developers.weixin.qq.com/community/pay/doc/000a063bb50380943c9cc755e51c00),大家可以去看一看,再回来看正确的过程。 正确的验签参数,请仔细看红色说明: [图片] 发一个小程序端领券成功的截图(虽然遇到新的问题,这个我还要去研究一下,哈哈): [图片]
2021-08-11 - 让小程序页面和自定义组件支持 computed 和 watch 数据监听器
习惯于 VUE 或其他一些框架的同学们可能会经常使用它们的 [代码]computed[代码] 和 [代码]watch[代码] 。 小程序框架本身并没有提供这个功能,但我们基于现有的特性,做了一个 npm 模块来提供 [代码]computed[代码] 和 [代码]watch[代码] 功能。 先来个 GitHub 链接:https://github.com/wechat-miniprogram/computed 如何使用? 安装 npm 模块 [代码]npm install --save miniprogram-computed [代码] 示例代码 [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, }, computed: { sum(data) { return data.a + data.b }, }, }) [代码] [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, sum: 2, }, watch: { 'a, b': function(a, b) { this.setData({ sum: a + b }) }, }, }) [代码] 怎么在页面中使用? 其实上面的示例不仅在自定义组件中可以使用,在页面中也是可以的——因为小程序的页面也可用 [代码]Component[代码] 构造器来创建! 如果你已经有一个这样的页面: [代码]Page({ data: { a: 1, b: 1, }, onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }) [代码] 可以先把它改成: [代码]Component({ data: { a: 1, b: 1, }, methods: { onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }, }) [代码] 然后就可以用了: [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, }, computed: { sum(data) { return data.a + data.b }, }, methods: { onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }, }) [代码] 应该使用 [代码]computed[代码] 还是 [代码]watch[代码] ? 看起来 [代码]computed[代码] 和 [代码]watch[代码] 具有类似的功能,应该使用哪个呢? 一个简单的原则: [代码]computed[代码] 只有 [代码]data[代码] 可以访问,不能访问组件的 [代码]methods[代码] (但可以访问组件外的通用函数)。如果满足这个需要,使用 [代码]computed[代码] ,否则使用 [代码]watch[代码] 。 想知道原理? [代码]computed[代码] 和 [代码]watch[代码] 主要基于两个自定义组件特性: 数据监听器 和 自定义组件扩展 。其中,数据监听器 [代码]observers[代码] 可以用来监听数据被 [代码]setData[代码] 操作。 对于 [代码]computed[代码] ,每次执行 [代码]computed[代码] 函数时,记录下有哪些 data 中的字段被依赖。如果下一次 [代码]setData[代码] 后这些字段被改变了,就重新执行这个 [代码]computed[代码] 函数。 对于 [代码]watch[代码] ,它和 [代码]observers[代码] 的区别不大。区别在于,如果一个 data 中的字段被设置但未被改变,普通的 [代码]observers[代码] 会触发,但 [代码]watch[代码] 不会。 如果遇到问题或者有好的建议,可以在 GitHub 提 issue 。
2019-07-24 - 在wxs里判断用户是安卓还是苹果系统(骚操作)
根据苹果和安卓时间格式,判断系统。 示例: [代码]tools.wxs[代码] var android = getDate(“2020-09-15 10:40:00”).getTime() === 1600137600000; if(android){ console.log(“安卓”) }else{ console.log(“苹果”) }
2020-09-15 - WXS功能文件共享
,在页面布局上难免会遇到要js处理数据的情况,我提供一份我目前使用的common.wxs给大家参考 module.exports = { jsonParse: function (str) { return JSON.parse(str); }, fixedFloatNumber: function (number, n) { return parseFloat(number.toFixed(n)) // 保留两位小数,末位为0时去掉 }, isEqualStrings: function (firstStr, nextStr) { var reg = getRegExp('[ ()-]', 'g') var str1 = firstStr.replace(reg, '') var str2 = nextStr.replace(reg, '') // console.log('bbb', str1, str2, str1 === str2) return str1 === str2 }, // params:倒计时的毫秒数 timeDifference: function(dateDiff){ // 剩余时间 var dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); var leave1=dateDiff%(24*3600*1000); var hours=Math.floor(leave1/(3600*1000)); var leave2=leave1%(3600*1000); var minutes=Math.floor(leave2/(60*1000)); if(dayDiff) { return dayDiff + '天'; }else if(hours) { return hours + '小时'; }else if(minutes) { return minutes + '分钟'; }else { return '0分钟'; } // params:2019-07-18 格式化时间 getFormatDate: function(params){ var currentYear = getDate().getFullYear(); var resultDate = ''; if(params && params.indexOf('-') !== -1) { var dateSplit = params.split('-') || []; if(dateSplit.length === 3) { var year = dateSplit[0]; if(currentYear == year) { resultDate = dateSplit[1] + '月' + dateSplit[2] + '日' }else { resultDate = dateSplit[0] + '年' + dateSplit[1] + '月' + dateSplit[2] + '日' } }else { resultDate = params; } }else { resultDate = params; } return resultDate; }, getTagsList: function(tags) { // 返回\分隔数组 var tagsList = tags.split('|') || []; return tagsList; }, getCityString: function(cities) { // 返回字符串,逗号隔开 var citysList = cities || []; return citysList.join(','); }, getCustomerDate: function(timestamp, language = 'CN') { // 传入时间戳,返回格式 x月x日 x时:x分(7月1日 09:01) var formatDate = ''; if(timestamp && timestamp.toString().length > 0) { var myDate = getDate(timestamp); var myYear = myDate.getFullYear(); var myMonth = myDate.getMonth() + 1; var myDay = myDate.getDate(); var myHours = myDate.getHours(); if(myHours.toString().length === 1){ myHours = "0" + myHours; } var myMinutes = myDate.getMinutes(); if(myMinutes.toString().length === 1){ myMinutes = "0" + myMinutes; } var currentDate = getDate(); var currentYear = currentDate.getFullYear(); if(language === 'CN' || language === 'cn') { if (myYear === currentYear) { formatDate = myMonth + '月' + myDay + '日' + ' ' + myHours + ':' + myMinutes; } else { formatDate = myYear + '年' + myMonth + '月' + myDay + '日' + ' ' + myHours + ':' + myMinutes; } } else { formatDate = myYear + '/' + myMonth + '/' + myDay + '/' + ' ' + myHours + ':' + myMinutes; } } else { formatDate = ''; } return formatDate; }, searchText: function (resourceStr, keyStr) { if(resourceStr && resourceStr.length > 0){ return resourceStr.indexOf(keyStr) !== -1; } return false } };
2020-07-10 - 小程序流量运营之微信搜一搜
悄悄地使用微信搜一搜,然后惊艳所有人 微信小程序的服务一直很强大,大部分人眼馋,主要是不知道怎么操作才能让自己的小程序出现在服务标签里,今天给大家细说一下。 本文阅读指引:1、看展示;2、看效果;3、学习操作;4注意事项 一、首先给大家看下展示效果(随便搜的非广告) [图片] 二、开通服务后的效果 我有一个小程序本来是要放弃的,名字起的一般,搜索量没有,每天用户不到10人,都准备注销了。但是开通服务后每天至少能达到100多人。下面是最近一周详细数据。 [图片] [图片] [图片] [图片] 看着数据是不是很带劲,对于日活上万的运营人员来说可能不算什么,但是对于一个即将放弃、没有流量的小程序来说,看着它日活从0-100的成就感还是很强的,可以毫不夸张的说,开通搜一搜服务让我的流量至少翻了10倍。 三、开通服务操作 3-1、硬件条件:认证的公众号超过6个月,只支持普通微信小程序(微店等小程序不支持) 3-2、公众号-微信搜一搜,开通服务搜索接入(下图) [图片] 3-3、然后转移到【服务搜索】添加服务,去提交小程序,审核通过即可(下图) [图片] [图片] 3-4、最后去品牌主页,编辑添加你的小程序去提交即可。(下图) [图片] 四、审核注意事项 下面内容均为我审核期间遇到的问题(非官方),如果审核失败没遇到我出现的问题,请自行解决,或者私信我帮你一起解决。 4-1、落地页不能有激励、插屏、视频、关注公众号、webview等其他广告 4-2、服务名称、服务标签要有服务意图 4-3、服务标签不能用大众化关键字,不能与其他服务关键字冲突 4-4、服务标签要与服务名称有相关性 4-5、自己体验落地页,要保证对用户服务的质量 4-6、落地页不能聚集服务,必须单服务和服务名称对应(同一个小程序多种服务,可多次提交不同页面) 4-7、服务图标不得含有品牌图标(不要侵权) 4-8、要分清自己的小程序是服务类还是内容类,内容类开通不了。例:头像制作类、表情类相关不支持,如果你有这类小程序的话,提审也会失败。 以下为不定期数据更新 20207月30日记录:29号下午开通了流量主,今天查看收益为4.99元,大部分用的是原生模板广告,无激励,用户数依旧呈现上升趋势。 [图片] [图片] see you next article
2020-07-30 - 开源的抽奖助手小程序
发现开源小程序之美三-抽奖助手小程序 发现开源小程序之美一,个人博客小程序 https://developers.weixin.qq.com/community/develop/article/doc/000a40e13ec550274e2a9addd56413发现开源小程序之美二,微慕WordPress小程序 https://developers.weixin.qq.com/community/develop/article/doc/000c44945dc728ab9c2aff2a55b013发现开源小程序之美三,抽奖助手小程序 https://developers.weixin.qq.com/community/develop/article/doc/0002846854056847b66a2d13451013发现开源小程序之美四,在线答题小程序 https://developers.weixin.qq.com/community/develop/article/doc/00040af07005609a223acee0151413发现开源小程序之美五,营销组件库 https://developers.weixin.qq.com/community/develop/article/doc/000c4235c98740a1dc2a1a6045b013发现开源小程序之美六,酱茄小程序 https://developers.weixin.qq.com/community/develop/article/doc/00040ede6d0388082a3aeb49b57813发现开源小程序之美七,二手书商场 https://developers.weixin.qq.com/community/develop/article/doc/0006ceb61a87182a4b3a1b32a5bc13发现开源小程序之美八,我要戴口罩https://developers.weixin.qq.com/community/develop/article/doc/0006a047b0cee0d5713ad731f5b813发现开源小程序之美九,失物招领小程序 https://developers.weixin.qq.com/community/develop/article/doc/000ca6a3b28ce8857b5a8bb3351c13发现开源小程序之美十,旅游攻略方面的微信小程序 https://developers.weixin.qq.com/community/develop/article/doc/000cc694e9c790ce755aee41556013 这个小程序是身边小伙伴开发的,基于云开发的一个抽奖助手小程序,我今天clone下代码,花了不到10分钟就运行成功了, 值得推荐给大家 先上截图大家参观下 [图片] 1 [图片] 2 [图片] 2 码云地址 https://gitee.com/xiaofeiyang3369/wechatlottery 我在调试过程中做了略微的改动 部署步骤建议按照下面三步走 第一步:创建集合,并将集合权限设置为:所有人可读,仅创建者可读写 第二步:将data里面的lottery.json文件导入到lottery集合 第三步:部署云函数 如没有意外就可以正常运行了,部署过程中遇到任何问题,请评论席留言。 更新记录 2020-07-20 重写了核心逻辑 ①开奖逻辑 ②抽奖逻辑 开奖逻辑目前是按照时间维度,到了时间不管人数有没有凑够都会进行开奖,开奖五分钟后,进行抽奖,确定中奖名额。 具体规则: (1)每个整点的1分去检测,根据当前时间检测是否有需要开奖的 (2)每个整点的5分去检测,是否有开奖未抽奖的,如果有,确定中奖名额 ~~
2021-01-25