- 小程序canvas的那些事
背景 业务场景需要在小程序内生成活动的分享海报,图片中的某些数据需动态展示。可行的方案有️二: 服务端合成:直接返回给前端图片URL 客户端合成:客户端利用canvas绘制 在当前业务场景下,使用客户端合成会优于服务端合成,避免造成不必要的服务器CPU浪费。 下面主要谈谈**客户端(canvas)**合成的过程。 实现思路 小程序端发起请求,获取需动态展示的数据; 利用canvas绘制画布; 导出图片保存到相册。 小技巧&那些坑 理想很丰满,现实很骨感。 实现思路很简单,然而,在实现过程中,发现会趟一些坑,也有一些小技巧,遂记录下来,以供参考。 promise化 画布的绘制依赖系统信息(自适应和优化图片清晰度)和动态数据。故画布需要在所有前置条件都准备完成时,方可绘制。为了提高代码优雅度和维护性,建议用promise化,避免回调地狱(Callback Hell)。 [代码] let promise1 = new Promise((resolve, reject) => { this.getData(resolve, reject) }); let promise2 = new Promise((resolve, reject) => { this.getSystemInfo(resolve, reject) }); Promise.all([promise1, promise2]).then(() => { this.drawCanvas() }).catch(err => { console.log(err) }); [代码] 自适应 1、为了在各个机型下保持大小自适应,需要计算出缩放比: [代码] getSystemInfo(resolve, reject) { try { const res = wx.getSystemInfoSync() //缓存系统信息 systemInfo = res //这里视觉基于iPone6(375*667)设计,2x图视觉,可以填写750,1x图视觉,可以填写375 zoom = res.windowWidth / 750 * 1 resolve() } catch (e) { // Do something when catch error reject("获取机型失败") } } [代码] 2、绘制时进行按缩放比进行缩放,如: [代码]ctx.drawImage(imgUrl, x * zoom, y * zoom, w * zoom, h * zoom) [代码] 绘制网络图片 经测试,绘制CDN图片需要先将图片下载到本地,在进行绘制: [代码]wx.downloadFile({ url: imgUrl, success: res => { if (res.statusCode === 200) { ctx.drawImage(res.tempFilePath, 326 * zoom, 176 * zoom, 14 * zoom, 14 * zoom) } } }) [代码] 绘制base64图片 因为业务上某些原因,依赖的图片数据,后端只能以base64格式返回给前端,而小程序在真机上无法直接绘制(开发工具OK)。 解决思路(存在兼容性问题,fileManager**基础库 1.9.9 **开始支持): 1、调用fileManager.writeFile存储base64到本地; 2、绘制本地图片。 实现代码如下: [代码]// 先获得一个文件实例 fileManager = wx.getFileSystemManager() // 把图片base64格式转存到本地,用于canvas绘制 fileManager.writeFile({ filePath: `${wx.env.USER_DATA_PATH}/qrcode.png`, data: self.data.qrcode, encoding: 'base64', success: () => { //此处需先调用wx.getImageInfo,方可绘制成功 wx.getImageInfo({ src: `${wx.env.USER_DATA_PATH}/qrcode.png`, success: () => { //绘制二维码 ctx.drawImage(`${wx.env.USER_DATA_PATH}/qrcode.png`, 207 * zoom, 313 * zoom, 148 * zoom, 148 * zoom) ctx.draw() } }) } }) [代码] 保存到本地相册 wx.saveImageToPhotosAlbum这个API需用户授权,故开发者需做好拒绝授权的兼容。此处实现对拒绝授权的场景进行引导。 [代码]canvas2Img(e) { wx.getSetting({ success(res) { if (res.authSetting['scope.writePhotosAlbum'] === undefined) { //呼起授权界面 wx.authorize({ scope: 'scope.writePhotosAlbum', success() { save() } }) } else if (res.authSetting['scope.writePhotosAlbum'] === false) { //引导拒绝过授权的用户授权 wx.showModal({ title: '温馨提示', content: '需要您授权保存到相册的权限', success: res => { if (res.confirm) { wx.openSetting({ success(res) { if (res.authSetting['scope.writePhotosAlbum']) { save() } } }) } } }) } else { save() } } }) function save() { wx.canvasToTempFilePath({ x: 0, y: 0, width: 562*zoom, height: 792*zoom, destWidth: 562*zoom*systemInfo.pixelRatio, destHeight: 792*zoom*systemInfo.pixelRatio, fileType: 'png', quality: 1, canvasId: 'shareImg', success: res => { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: () => { wx.showModal({ content: '保存成功', showCancel: false, confirmText: '确认' }) }, fail: res => { if (res.errMsg !== 'saveImageToPhotosAlbum:fail cancel') { wx.showModal({ content: '保存到相册失败', showCancel: false }) } } }) }, fail: () => { wx.showModal({ content: '保存到相册失败', showCancel: false }) } }) } [代码] 导出清晰图片 wx.canvasToTempFilePath有destWidth(输出的图片的宽度)和destHeight(输出的图片的高度)属性。若此处以canvas的宽高去填写的话,在高像素手机下,导出的图片会模糊。 原因:destWidth和destHeight单位是物理像素(pixel),canvas绘制的时候用的是逻辑像素(物理像素=逻辑像素 * density),所以这里如果只是使用canvas中的width和height(逻辑像素)作为输出图片的长宽的话,生成的图片width和height实际上是缩放了到canvas的 1 / density大小了,所以就显得比较模糊了。 这里应该乘以设备像素比,实现如下: [代码]wx.canvasToTempFilePath({ x: 0, y: 0, width: 562*zoom, height: 792*zoom, destWidth: 562*zoom*systemInfo.pixelRatio, destHeight: 792*zoom*systemInfo.pixelRatio, fileType: 'png', quality: 1, canvasId: 'shareImg' )} [代码] 特殊字体的绘制 研究发现,目前小程序canvas无法支持设置特殊字体,而业务生成的海报,又期望以特殊字体去呈现,最终取了个折中方案——保留数字部分的特殊样式。 实现方式为:把0-9这10个数字单独切图,用ctx.drawImage API,以图片形式去绘制。 [代码]drawNum(num, x, y, w, h) { return new Promise(function (resolve, reject) { //这里存储0-9的图片CDN链接 let numMap = [] wx.downloadFile({ url: numMap[num], success: res => { if (res.statusCode === 200) { ctx.drawImage(res.tempFilePath, x * zoom, y * zoom, w * zoom, h * zoom) resolve() } }, fail: () => { reject() } }) }) } [代码] 安卓机型图片绘制锯齿化问题 测试发现,同样的绘制方案,在安卓下,调用ctx.drawImage方法,图片会出现锯齿问题。测试还发现,原像素越高,锯齿化程度降低(但业务上使用太大像素的素材也不合理),这里需要客户端底层进行优化,目前没有找到合适的解决方案。 总结 个人觉得,目前小程序canvas就底层能力上相比web还有一些不足。所以应注意两点: 提前从业务出发,考虑当前实现的可行性,以便采取更优方案(如特殊字体,像素要求等); 若绘制canvas导出图片是个高频场景,可参考html2canvas进行封装,以便提高效能(SelectorQuery节点查询需1.9.90以上)。 ps:之前有想过利用web-view方式,在传统网页去绘制,然后通过web-view和小程序的通信来实现的方式。时间原因,并未尝试,感兴趣同学可以尝试下。
2019-03-07 - 如何通过小程序实现跨平台开发
背景 前段时间要做一系列的测试工具,需要在多平台:iOS、android、H5、公众号、小程序都实现。功能基本一样,就是在支付步骤需要区分平台,用对应的支付方式支付。本文讨论如何用一套小程序代码实现上述5个平台的开发。 效果图(左边为小程序,右边为浏览器): [图片] omi-mp的介绍 omi-mp是腾讯前端框架omi的一个工具集,其目的是在于将小程序代码转成H5/Web,具体可以参见omi-mp的介绍Github。 omi-mp的转换并不是完全兼容小程序所有特性的,只支持了一小部分小程序API,并且存在了一些兼容特性,因此就需要开发者在开发小程序代码时,更多的以开发H5/Web的思路开发。 思路图和实现步骤 [图片] 先实现小程序代码 1.初始化omi-mp目录工程: [代码]npm i omi-cli -g omi init-mp {工程名称} cd {工程名称} npm install [代码] 2.把小程序项目拷贝到[代码]src-mp[代码]目录。 3.建议边实现小程序的过程中,不断的检验生成的H5的正确性,避免在最后阶段检验,否则如果出现问题,将不好定位,本地运行H5命令: [代码]npm start //开发 [代码] 再将小程序打包成H5/Web 打包H5/Web命令: [代码]npm run build //发布 [代码] 发布需要确认域名,修改[代码]package.json[代码]文件,修改[代码]"build": "PUBLIC_URL={发布域名} node scripts/build.js"[代码] 如果存在部分js文件丢失,可以尝试执行 [代码]gulp copyThen [代码] 公众号直接加载H5 公众号本质上也属于H5。 iOS/android App通过内嵌网页加载H5 iOS通过MKWebView加载H5。 android通过WebView加载H5。 H5和原生App的交互部分可以通过JSBridge或者URL拦截实现 小程序代码如何区分平台 综上所述,除去部分iOS/android的原生代码外,基本所有的逻辑都是放在小程序里,按不同平台实现不同逻辑,小程序可以通过UserAgent以及Dom区分: 如果Dom树不存在[代码]window[代码]或者[代码]document[代码],为小程序平台; iOS/android App内嵌网页可以自定义特殊的UserAgent,小程序代码可以通过此来区分iOS/android App平台; 微信App内嵌浏览器的UserAgent会带入[代码]MicroMessenger/[代码]关键字,可以按此区分公众号平台; 其余为H5/Web平台; [代码]if (typeof window == 'undefined') { // 小程序 } else { if (navigator.userAgent.userAgent.indexOf('ios-app') != -1 || navigator.userAgent.userAgent.indexOf('android-app') != -1) { // iOS/android } else if (this.globalData.userAgent.indexOf('MicroMessenger/') != -1) { // 公众号 } else { // Web } } [代码] iOS/android原生代码如何桥接 1.iOS/android需要先设置特殊UserAgent让小程序代码知道是iOS/android平台 iOS: [代码]NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"ios-app", @"UserAgent", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary]; [代码] android: [代码]webView.getSettings().setUserAgentString("android-app"); [代码] 2.以URL拦截为例,小程序代码在当处于iOS/android平台的情况下,可以发送特殊URL,让iOS/android原生代码处理: 小程序: [代码]if (navigator.userAgent.userAgent.indexOf('ios-app') != -1 || navigator.userAgent.userAgent.indexOf('android-app') != -1) { let url = 'app://pay?' + query; window.location.href = url; } [代码] 3.iOS/android拦截URL特殊处理 iOS: [代码]- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSURL * url = navigationAction.request.URL; if ([url.absoluteString hasPrefix:@"app://pay"]) { // do something... decisionHandler(WKNavigationActionPolicyCancel); return; } decisionHandler(WKNavigationActionPolicyAllow); } [代码] android: [代码]webView.setWebViewClient(new WebViewClient(){ public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("app://pay")) { // do something... return true; } return false; } }); [代码] omi-mp的部分缺陷和一些踩过的坑 1.目前omi-mp只支持部分小程序部分API: [代码]- wx.request - wx.navigateTo - wx.navigateBack - wx.getSystemInfo - wx.getSystemInfoSync - wx.setNavigationBarTitle - this.setData - this.triggerEvent [代码] 如果用了其他的API,那么将会在输出H5报错。 2.支持组件,但是不支持组件的函数直接调用,替代方案可以用mitt,在page和component之间用mitt消息传递。 3.如果需要同时输出H5和Web,那么需要同时绑定[代码]click[代码]和[代码]tap[代码]事件(小程序只能绑定tap,Web只能绑定click,H5两者都可以),但是同时绑定又将会造成在H5的情况下,[代码]click[代码]和[代码]tap[代码]都会回调,导致两次调用。解决办法是可以在[代码]click[代码]和[代码]tap[代码]的地方同时加上判断,避免两次调用: [代码]handleTap(e) { if (!((typeof window == 'undefined') && e.type === "tap")) { return; } else if (!((typeof window != 'undefined') && e.type === "click")) { return; } // do something... } [代码] 4.wxml不支持Object字段的遍历处理。 5.如果编译不过,那么确认下是不是wxml中存在了一些特殊关键字,与omi的重了,导致失败。 6.即便在H5的场景下也无法使用[代码]document.getElementById()[代码]。但是有替代方案,只是比较麻烦。 7.还有其它缺陷,有些忘了,待补充。 结语 omi-mp是一个不错的工具,在小程序不断变大变强的今天,能做到一套小程序代码,多端运行,降低开发成本。这里尤其感谢dntzhang的大力支持,希望omi越做越好。
2019-04-12 - 云开发-保存网络文件到云开发存储
技术 小程序云开发 NodeJS 开始 新建小程序云开发项目,开通云开发; 在cloudfunctions下新建Node.js云函数,命名download; 进入新建的云函数文件目录下(电脑磁盘download文件夹下),执行; 参考:https://www.npmjs.com/package/request-promise npm install request-promise 打开新建云函数index.js文件内容 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const rp = require('request-promise'); cloud.init() // 云函数入口函数 exports.main = async(event, context) => { var options = { // 请求方式 method: 'POST', // 请求地址 uri: 'https://image.qq.com', // 请求header headers: { 'Content-type': 'application/json' }, // 是否返回完整response resolveWithFullResponse: true, // 发送的请求参数 body: { "text": event.text, "voice": event.voice }, json: true, // encoding 当请求出现问题时,可以设置此参数,尝试解决 encoding: null }; const response = await rp(options); var time = new Date(); // 请求成功返回结果,判断是否成功 // if (response.headers['content-type'] == 'audio/mpeg') { const upload = await cloud.uploadFile({ //保存文件路径 cloudPath: time.getTime() + '.jpg', //文件流 fileContent: response.body, }); return { event, upload } } [代码] 在miniprogram文件夹下的pages中新建Page,命名index,打开index.js,调用云函数 [代码]// miniprogram/pages/index/index.js Page({ /** * 生命周期函数--监听页面加载 */ onLoad: function(options) { this.downloadImg(); }, downloadImg() { wx.cloud.callFunction({ // 要调用的云函数名称 name: 'download', // 传递给云函数的参数 data: { text: that.data.text, voice: that.data.voice }, success: res => { console.log(res) } }); } }) [代码]
2019-03-04 - “新闻联播”上线小程序,看看“国家队”如何花式做内容?
说起来你可能不信,《新闻联播》也做小程序了! 从1978年起,《新闻联播》就是新闻节目中最权威、最有知名度的存在,40年如一日的给全国人民播报新闻。这种严肃正统的形象,很难让观众将其与科技、创新联系在一起。 “新闻联播+”小程序就这样在所有人都没想到的情况下悄然上线了,正所谓越是破次元壁的融合,越能带来惊喜。 [图片] 经过一番“暴力测试”后,我们发现这款小程序竟然是个集节目回顾、经常点评、新闻周边于一体的产品,这要是安在电视剧上,就是个粉丝大本营。 360°了解“新闻联播” 在传统印象里,时长只有半小时的新闻联播,播完就没法回看。而这款小程序的设计目的就是让内容形式变丰富,满足不同用户的需求。 首先,节目分成看、听、读三种形式。 看联播:手机里在线观看当日新闻,还能点击下方新闻标题,直接观看该则新闻; 听联播:不方便看?可以选择听新闻; 三分钟速览:将半小时的节目浓缩成3分钟可阅读完的文章。 [图片] 这种模式其实很常见,很多电视剧都在用这招。比如,现在很多视频App里,会针对热门电视剧剪辑出经典片段或三分钟看一集的内容形式,都是为了方便用户快速获取有用的内容。 其次,用搜索和订阅“捆”住用户 在小程序里,用户可以搜索关键词,一次性查看所有跟关键词相关的新闻,相信很多要写论文的朋友对这个功能十分喜欢,毕竟出自“新闻联播”里的话绝对不会出错;而订阅功能则是满足只对某些新闻感兴趣的用户。 [图片] 同时,小程序里还能按日期搜索,这就相当于按集数搜索电视剧的感觉,非常精准。 值得一说的是,这款小程序里还巧妙的用到新闻“周边”的玩法,其主要提现央视快评和懂联播这两个板块上。其实就是对新闻精选点评和深度解读,让用户了解新闻背后或者深入了解新闻。从这个功能来看,这款小程序是把新闻联播与焦点访谈合二为一,为用户提供不同层次的使用体验。 [图片] 但体验过后,我们发现这款小程序在互动方面并不明显,也较少的尝试与年轻用户交流,站在评价一款优质小程序的角度来看,还有很多玩法可以尝试。 传统媒体里的小程序新玩法 如今传统媒体上线小程序,已经不再是凤毛麟角,许多看似“古板”的报刊、杂志都用小程序触及了不少新人群。它们在拉新、互动等方面,有着自己的一套打法,或许“新闻联播+”小程序在下个版本可以借鉴一下。 1、观点PK,让正能量先行 获取年轻用户,需满足年轻人喜好,南方都市报推出的“热点站站队”小程序用观点PK的角度,吸引用户表达意见,同时,在小程序里被大家推崇的观点,基本都非常正能量。 [图片] 表达这件事这对年轻用户非常有号召力,“热点站站队”也正是通过小程序放大这一元素,让用户为新闻表达观点,为正义发声。 2、现场“直播”,让体验身临其境 “新华社微悦读”大家一定不陌生了,这款小程序曾多次被我们提到,也在微信公开课上大放光彩。在小程序里有个“现场”的栏目,每天上线当日最新的资讯,同时用户评论后,会以弹幕形式在新闻中出现,对于早已习惯弹幕的年轻用户来说,这种玩法无疑是点睛之笔。 [图片] 3、让报纸变成学习工具 作为报纸界的权威——中国日报,为了增加用户的粘性,推出“中国日报网英语点津”小程序。 简单的新闻资讯小程序,在这里摇身一变成了满足用户英语学习的工具,对于爱学习的用户来说,每天登陆这款小程序不仅能了解天下大事,还能提高英语能力。同时,对小程序而言,这也是增加用户粘性的大好机会。 [图片] 4、让小程序成为节目的延伸 “新闻联播+”小程序从新闻角度上已经成为节目的延伸,但在小程序里,延伸的方式不只这一种。 去年《国家宝藏》引发全民热议,很多用户遗憾不能亲自去当地博物馆感受历史的底蕴。也有许多博物馆因节目的播出,导致门票一票难求。而这些痛点,早已被小程序解决。 在“国宝微展示”小程序里,节目里出现过的宝藏,都能通过小程序里全方位欣赏,不论是调整角度还是放大缩小,都能欣赏3D高清大图。在配上小程序里的背景音乐和解读,这体验比亲自去博物馆看还厉害。 [图片] 这款小程序在我们看来,搭配电视节目一起看在合适不过。 从传统媒体开发小程序,其目的必然与拓展新用户群体分不开,而以上这些例子可以看到,只打一个点会更有吸引力。南方都市报 - 表达;新华社微悦读 - 直播;中国日报 - 学习;国家宝藏与新闻联播 - 内容延伸。 也正是因为这些独特玩法,才让这些小程序脱颖而出,成为传统媒体在小程序领域的佼佼者。
2019-03-04 - 小程序实现大转盘,九宫格抽奖,带跑马灯效果
基本实现功能 1,小程序仿天猫超市大转盘 2,九宫格转盘抽奖 3,积分抽奖 4,抽到的积分随机生成 5,抽奖结果可以同步到服务器(小程序云开发后台) 老规矩先看效果图 [图片] 简单说一下实现原理. 我们借助js的定时器,来执行一个加法。比如我们设置一个上限300,每过一定时间执行一次,然后我们再做一个随机数,这个随机数不停的++,直到总数大于300.就代表抽奖结束。核心代码如下。 [代码] //开始抽奖 startGame: function() { if (this.data.isRunning) return this.setData({ isRunning: true }) var _this = this; var indexSelect = 0 var i = 0; var timer = setInterval(function() { indexSelect++; let randomNum = Math.floor(Math.random() * 10) * 10; //可均衡获取0到90的随机整数 i += randomNum; if (i > 300) { //去除循环 clearInterval(timer) //获奖提示 let jifen = 1; let selectNum = _this.data.indexSelect console.log("选号:" + selectNum ); if (selectNum===0) { jifen = 2; } else if (selectNum === 1) { jifen = 3; } else if (selectNum === 2) { jifen = 4; } else if (selectNum === 3) { jifen = 5; } else if(selectNum === 4) { jifen = 6; } else if(selectNum === 5) { jifen = 8; } else if (selectNum === 6) { jifen = 10; } wx.showModal({ title: '恭喜您', content: '获得了' + jifen + "积分", showCancel: false, //去掉取消按钮 success: function(res) { if (res.confirm) { _this.setData({ isRunning: false }) } } }) } indexSelect = indexSelect % 8; _this.setData({ indexSelect: indexSelect }) }, (200 + i)) } [代码] 完整源码可以加我微信,如果有关于小程序的问题,可以加我微信2501902696(备注小程序)
2019-03-05 - 从源码看微信小程序启动过程
一、写作背景 接触小程序一年多,真实体验就是小程序开发门槛相对而言确实比较低。不过小程序的开发方式,一直是开发者吐槽的,如习惯了 Vue,React 开发的开发者经常会吐槽小程序一个 Page 必须由多个文件组成,组件化支持不完善或者说不能非常愉快的开发组件。在以前小项目中没太大感觉,从加入有赞,参与有赞微商城小程序的开发,是真切的体会到对于大型小程序项目开发的复杂性。 有赞从微信小程序内测就开始开发小程序,在不支持自定义组件的时代,只能通过 import 的形式拆分模块或实现组件。在业务复杂的页面,可能会 import 非常多的模块,而相应的 wxss 也需要 import 样式,除了操作繁琐,有时候也难免遗漏。 作为开发者,我们当然希望可以让工作更简单,更愉快,也希望改善我们的开发方式。所以希望能够更了解微信小程序框架,减少不必要的试错,于是有了一次对小程序框架的 debug 之旅。(基础库 1.9.93) 通过三周空余时间的 debug,也算对小程序框架有了一些浅显的认识,达到了最初的目的;对小程序启动,实例,运行等有了真切的体会。这篇文章记录了小程序框架的基本代码结构,启动流程,以及程序实例化过程。 本文的目的是希望把我看到的分享给对小程序感兴趣或者正在开发小程序的读者,主要解答“框架对传入的对象等到底做了什么”。 二、从启动流程一窥小程序框架细节 在开发者工具中使用 help() 方法,可以查看一些指令和方法。使用其中的 openVendor 方法可以打开微信开发者工具在小程序框架所在目录。其中以包括以基础库命名的目录和其他帮助文件,如其中有两个工具 wcc,wcsc。wcc 可把 wxml 转换为对应的 JS 函数 —— $gwx(path, global),wcsc 可将 wxss 转换为 css。而基础库目录包括 WAService.js 和 WAWebview.js 文件。小程序框架在开发者工具中以 WAService.js 命名(WAWebview.js 不知其作用,听说在真机环境使用该文件)。 在开发中工具命令行使用 document.head 可以查看到小程序的启动流程大致如下: [图片] 以小节的方式分别介绍这些流程,小程序是如何处理的(小节编号与图中编号相同)。 1、初始化全局变量 下图是小程序启动是初始化的一些全局的变量: [图片] 那些使用“__”开头,未在文档中提及可使用变量是不建议使用的,wxAppCode 在开发者工具中分为两类值,json 类型和 wxml 类型。以 .json 结尾的,其 key 值为开发者代码中对应的 json 文件的内容,.wxml 结尾的,其 key 值为通过调用 $gwx(’./pages/example/index.wxml’) 将得到一个可执行函数,通过调用这个函数可得到一个标识节点关系的 JSON 树。 [图片] 2、加载框架(WAService.js) 使用工具对 WAService.js 进行格式化后进行 debug。可以发现小程序框架大致由: WeixinJSBridge、 NativeBuffer、 wxConsole、 WeixinWorker、 JavaScript兼容(这部分为猜测)、 Reporter、 wx、 exparser、 virtualDOM、 appServiceEngine 几部分组成。 其中除了 wx 和 WeixinJSBridge 这两个基础 API 集合, exparser, virtualDOM, appServiceEngine 这三部分作为框架的核心, appServiceEngine 提供了框架最基本的接口如 App,Page,Component; exparser 提供了框架底层的能力,如实例化组件,数据变化监听,view 层与逻辑层的交互等;而 virtualDOM 则起着链接 appServiceEngine 和 exparser 的作用,如对开发者传入 Page 方法的对象进行格式化再传入 exparser 的对应方法处理。 框架对外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。 3、业务代码的加载 在小程序中,开发者的 JavaScript 代码会被打包为 [代码]define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) { 'use strict'; // your code }) [代码] 这里的 define 是在框架中定义的方法,在框架中提供了两个方法:require 和 define 用来定义和使用业务代码。其方式有些像 AMD 规范接口,通过 define 定义一个模块,使用 require 来应用一个模块。但是也有很大区别,首先 define 限制了模块可使用的其他模块,如 window,document;其次 require 在使用模块时只会传入 require 和 module,也就是说参数中的其他模块在定义的模块中都是 undefined,这也是不能在开发者工具中获取一些浏览器环境对象的原因。 在小程序中,JavaScript 代码的加载方式和在浏览器中也有些不同,其加载顺序是首先加载项目中其他 js 文件(非注册程序和注册页面的 js 文件),其次是注册程序的 app.js,然后是自定义组件 js 文件,最后才是注册页面的 js 代码。而且小程序对于在 app.js 以及注册页面的 js 代码都会加载完成后立即使用 require 方法执行模块中的程序。其他的代码则需要在程序中使用 require 方法才会被执行。 下面详细介绍了 app.js,自定义组件,页面 js 代码的处理流程。 4、加载 app.js 与注册程序 在 app.js 加载完成后,小程序会使用 require(‘app.js’) 注册程序,即对 App 方法进行调用,App 方法是对 appServiceEngine.App 方法的引用。 下图是框架对于 App 方法调用时的处理流程: [图片] App 方法根据传入的对象实例化一个 app 实例,其生命周期函数 onLaunch 和 onShow 因为使用不同的方式获取 options的参数。在有些需要根据场景值来实现需求的,或许使用 onShow 中的场景值更合适。 在实际开发过程中发现,在微信顶部唤起小程序和在小程序列表唤起的 options 也是不一样的。在该案例中通过点击分享的小程序进入后,关闭小程序,再通过不同方式进入小程序,通过顶部唤起的还是 options 的 path 属性还是分享出来的 path,但是通过列表中打开直接回到了首页,这里 App 中的 onShow 就会获取到不同的 options。 5、加载自定义组件代码以及注册自定义组件 自定义组件在 app.js 之后被加载,小程序会在这个过程中加载完所有的自定义组件(分包中自定义组件没有有测试过),并且是加载完成后自动注册,只有注册完成后才会加载下一个自定义组件的代码。 下图是框架对于 Component 方法处理流程: [图片] 图中介绍了框架如何对传入 Component 方法的对象的处理,其后面还有很多深入的对于组件实例化的步骤没有在图中表示出来,具体可以在文章最后的附件中查看。 自定义组件在小程序中越来越完善,其拥有的能力也比 Page 更强大,而后面会提到在使用自定义组件的 Page 中,Page 实例也会使用和自定义组件一样的实例化方式,也就是说,他拥有和自定义组件一样的能力。 6、加载页面代码和注册页面 加载页面代码的处理流程和加载自定义组件一样,都是加载完成后先注册页面,然后才会加载下一个页面。 下图是注册一个页面时框架对于 Page 方法的处理流程: [图片] Page 方法会根据是否使用自定义组件做不同的处理。使用自定义组件的 page 对象会被处理为和自定义组件的结构,并在页面实例化时使用不同的处理流程进行实例化。当然对于开发而言没任何不同。 从图中可以发现 Page 传入的(生命周期)代码并不会在这里被执行,可以通过下面小节了解 Page 实例化的详细过程。 7、等待页面 Ready 和 Page 实例化 还记得上面介绍的启动流程中最后一步等待页面 Ready?严格来讲是等待浏览器 Ready,小程序虽然有部分原生的组件,不过本质上还是一个 web 程序。 在小程序中切换页面或打开页面时会触发 onAppRoute 事件,小程序框架通过 wx.onAppRoute 注册页面切换的处理程序,在所有程序就绪后,以 entryPagePath 作为入口使用 appLaunch 的方式进入页面。 下图是处理导航的程序流程: [图片] 从图中可以看出页面的实例化是在进入页面时进行,下图是具体的实例化过程: [图片] 下图是最终可得到 Page 实例: [图片] 可以发现其中多了 onRouteEnd API,实际该接口不会被调用。其中以 component 标记的表示只有在使用了自定义组件时才会有的方法和属性。在前面第 5 小节提到了对于使用自定义组件的页面会按照自定义组件方式解析,这些属性和方法与自定义组件表现一致。 8、关于 setData 小程序框架是一个以数据驱动的框架,当然不能少了对他如何实现数据绑定的探索,下图是 Page 实例的 setData 执行流程: [图片] 其中 component:setData 表示使用自定义组件的 Page 实例的 setData 方法。 三、写在最后 这是一次不完全的小程序框架探索,是在微信开发工具中 debug 的结果。虽然对于实际开发没有什么太大的帮助,但是对框架如何对开发的 js 代码进行处理有了一个很明确的认识,在使用一些 js 特性时可以有明确的感知。如果你还疑惑“小程序框架对传入的对象等到底做了什么”那一定是我表达能力太差,说声对不起。 通过这一次 debug ,也给我引入了新的问题,还希望能够有更多的讨论: · 自定义组件太多启动时会耗时处理自定义组件 · 文件太多会耗时读文件 · 合理的设计分包很重要 当然最后对于框架中已有的能力,还是非常希望微信可以开放更多稳定的接口,并在文档中告知开发者,让开发变得简单一些。
2019-03-05 - 一个云函数包含所有数据表的增、删除、查,一个简单的方法处理页面中不同函数周期调用数据的过程
今天写代码时突发奇想我要把一个列表页的,onShow,onPullDownRefresh,和onReachBottom里加载数据的事件写成一个共同的方法来调用。而三个方法的不同之处在于onShow是每次页面显示时要刷新数据,取得最新的n条数据,而onPullDownRefresh是页面下拉时也是要刷新,但不同之处在于数据刷新完成后应该停止下拉执行wx.stopPullDownRefresh();而onReachBottom是页面上拉触底时应该去取跳过已经加载到页面里的数据外的下一个n条数据。 云函数 [代码]const cloud = require('wx-server-sdk') cloud.init(); const db = cloud.database() const _ = db.command // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() switch (event.acType) { case 'add': { //插入记录固定增加操作人的openid和unionid和创建时间,其它字段由传数值来确定 event.addData.OPENID = wxContext.OPENID; event.addData.UNIONID = wxContext.UNIONID; var _createTime = db.serverDate(); event.addData.createTime = _createTime; return db.collection(event.tableName).add({ data: event.addData }); } //通用所有记录 case 'alllist': {//取跳过m条数据后的n条数据 return db.collection(event.tableName).orderBy('createTime', 'desc').skip(event.m).limit(event.n).get(); } //单个详细 case 'doc': { return db.collection(event.tableName).doc(event._id).get(); } //创建者列表 case 'mylist': { var openid = wxContext.OPENID; return db.collection(event.tableName).where({ OPENID: openid }).orderBy('createTime', 'desc').skip(event.m).limit(event.n).get(); } //删除单条 case 'remove': { return db.collection(event.tableName).doc(event._id).remove(); } default: {//保留原示例代码以供参考基础 return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } } } [代码] 页面统一加载方法 [代码]loaddata: function (m = 0, comac = null) { var that = this; wx.showLoading({ title: '数据加载中...', }) wx.cloud.callFunction({ name: 'schoolHander', data: { acType: 'alllist', tableName: 'Schools', m: m, n: 20 } }).then(res => { if (m == 0) {//如果跳过记录数为0寻么认为是刷新或首次加载,直接把结果赋值 that.setData({ alllist: res.result.data }) } else {//跳过行不为0,应该是追加记录 that.setData({ alllist: that.data.alllist.concat(res.result.data) }) } wx.hideLoading(); if (comac) { comac();//如果执行完成后要附加什么方法就执行 } }).catch(err => { wx.hideLoading(); if (comac) { comac();//如果执行完成后要附加什么方法就执行 } }) } [代码] 调用方法 [代码] /** * 生命周期函数--监听页面显示 */ onShow: function () { this.loaddata();//因为所有参数都有默认值所以不传自动按默认值首次加载处理 }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { //刷新数据,但完成后应该要停止下拉事件,不然经常会出现页面不自动收上去的情况 this.loaddata(0,() => { wx.stopPullDownRefresh(); }); }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { 所跳过数据行的值设置成当前页只已经存在的数据条数 this.loaddata(this.data.alllist.length,null); } [代码] 小小分享,献丑了_
2019-02-27 - 小程序架构设计(二)
接着上篇文章《小程序架构设计(一)》 前边我们说到采用Web+离线包的方式可以解决很多问题,但是遗留了一个安全问题有待解决。 经过了一番讨论,我们决定把开发者的JS逻辑代码放到单独的线程去运行,因为不在Webview线程里,所以这个环境没有Webview任何接口,自然的开发者就没法直接操作Dom,也就没法动态去更改界面,“管控”的问题得以解决。 还存在一个问题:开发者没法操作Dom,如果用户交互需要界面变化的话,开发者就没办法动态变化界面了。所以我们要找到一个办法:不直接操作Dom也能做到界面更新。 其实Facebook早有方案解决这个问题,就是上篇文章提到的React。React引入了Virtual Dom的概念(后文简称VD),业务侧只需要改变数据即可引起界面变化,相关原理后边再写篇文章来分享。 至此小程序双线程的模型就定下来了:渲染层(Webview)+逻辑层(JSCore) [图片] 其中渲染层用了Webview进行渲染,开发者的JS逻辑运行在一个独立的JSCore线程。 渲染层提供了带有数据绑定语法的WXML,逻辑层提供了setData等等API,开发者需要进行界面变化时,只需要通过setData把变化的数据传进去,小程序框架就会进行Dom Diff等流程最后把正确的结果更新在Dom树上。 [图片] 可以看到在开发者的逻辑下层,还需要有一层小程序框架的支持(数据通信、API、VD算法等等),我们把它称为基础库。 我们在两个线程各自注入了一份基础库,渲染层的基础库含有VD的处理以及底层组件系统的机制,对上层提供一些内置组件,例如video、image等等。逻辑层的基础库主要会提供给上层一些API,例如大家经常用到的wx.login、wx.getSystemInfo等等。 解决了渲染问题,我们还要看一下用户在和界面交互时的问题。 [图片] 用户在屏幕点击某个按钮,开发者的逻辑层要处理一些事情,然后再通过setData引起界面变化,整个过程需要四次通信。对于一些强交互(例如拖动视频进度条)的场景,这样的处理流程会导致用户的操作很卡。 对于这种强交互的场景,我们引入了原生组件,这样用户和原生组件的交互可以节省两次通信。 [图片] 正如上图所示,原生组件和Webview不是在同一层级进行渲染,原生组件其实是叠在Webview之上,想必大家都遇到过这个问题,video、input、map等等原生组件总是盖在其他组件之上,这就是这个设计带来的问题。 我们也很重视这个问题,经过了一段时间的努力,我们攻克了这个难题,把原生组件渲染到Webview里,从而实现同层渲染。目前video组件已经完成同层渲染的全量发布,详细可以看我们之前的公告:同层渲染公测。 为了让开发者可以更好的开发小程序,我们在后来还引入了自定义组件和插件的概念,我们后续会有相关的文章再介绍这两块的设计,希望大家关注我们社区的文章板块。 [图片] 以上就是小程序架构设计的历史。
2019-02-27 - 聊一聊小程序开发中的单位如何布局使用?
小程序支持的单位? 可以说小程序就是在微信体系网页的另一种表现方式。网页中的单位小程序基本都支持。但实际开发中,我常用到的是以下几种 ↓ rpx rpx做为小程序自家系统里的单位,特性是可以根据屏幕宽度进行自适应。rpx官方介绍 比如我写一个2:1比例的全屏轮播图,可以这样写: [代码]swiper { width:750rpx; height:375rpx; } [代码] 1rpx = 0.5px = 1物理像素。网页开发中,默认字体一般设置为14px,在小程序中我们就可以设置小程序的默认字体大小为28rpx。 px 在小程序开发中 rpx基本就代替了px,但在一些特殊的场合,px的表现要比rpx好。 兼容ipad时,由于ipad可以横屏和竖屏,并且屏幕宽度可以达到2K以上,如果你的小程序要考虑到兼容ipad,那么还是多考虑使用px吧。 覆盖微信原生组件样式。em????可以覆盖微信原生样式??? 是的,只有小程序老玩家才知道的秘密!小程序原生样式是可以覆盖美化的,以 <switch> 组件为例:switch代码片段 [图片] 导入代码片段到开发者工具中,并切换设备模式预览可以发现rpx表现不佳。使用px反而更好。 em与rem em与rem在H5的网页开发上可以大放异彩,但小程序中因为有rpx的存在,em与rem使用的就少了。基本只有在一些对字体宽度有特效的情况下才会使用。比如首行缩进。 vw、vh和百分比 vw:视窗宽度,1vw等于视窗宽度的1%。 vh:视窗高度,1vh等于视窗高度的1%。 %:父级容器的宽度百分百。 [图片] calc() 的使用 前面讲了单位,那么我们现在来聊聊怎么使用这些单位了。小程序是网页的一种,支持css,也支持calc()。 这里吃下书: calc() 函数用于动态计算长度值。 [代码] ● 需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px); ● 任何长度值都可以使用calc()函数进行计算; ● calc()函数支持 "+", "-", "*", "/" 运算; ● calc()函数使用标准的数学运算优先级规则; [代码] 使用场景示例 垂直导航页,常用于外卖订餐或者商城的二级分类页。 上半部分是定死高度375rpx的轮播图区域,下半部分是可以随设备高度变化的可滚动的区域。容器高度可以这样写: [代码]{ height:calc(100vh - 375rpx) } [代码] [图片] 结尾 夜深了,晚安,不定期更新小程序使用技巧。新人写文章,大佬多指点! [图片]
2019-02-26 - 1个开发如何撑起一个过亿用户的小程序
2018年12月,腾讯相册累计用户量突破[代码]1亿[代码],月活1200万,阿拉丁指数排行 [代码]Top 30[代码],已经成为小程序生态的重量级玩家。 三个多月来,腾讯相册围绕【在微信分享相册照片】这一核心场景,快速优化和新增一系列社交化功能,配合适当的运营,实现累计用户量突破[代码]1亿[代码],大大超过预期。 [图片] (腾讯相册用户量破亿) 可是,谁曾想到,这样一个亿级体量的小程序,竟然是一个开发做出来的?他又是有哪般“绝技”,可以一个人撑起一个用户过亿的小程序? 后台人力紧缺,怎么办? 当我第一次见到腾讯相册小程序的开发David(化名)时,他显得忧心忡忡。 “年底的目标是要过千万的用户,但现在只有几位前端和后台开发。不仅如此,我们的后台开发还不是百分百能够投入到这个项目,大部分时间要抽身支援其它项目,人力非常紧缺。此外,原有后台系统有不少历史包袱,在原有架构上做新的社交化功能开发是不现实的。怎么办? “要不试试’小程序·云开发’吧,只需要前端就可以把小程序搞起,正好解决我们缺后台的难题。” 于是,David作为腾讯相册前端开发团队的骨干,担当起用小程序·云开发实现腾讯相册小程序社交化功能的重任。 “第一次接触到’小程序·云开发‘时,觉得这个东西(小程序·云开发)理念挺新颖的———小程序无服务开发模式。在一般的小程序开发中,有三大功能小程序开无法绕开后台的帮助,它门分别是数据读取、文件管理以及敏感逻辑的处理(如权限)。因此,传统的开发模式,在小程序端都必须发送请求到后台进行鉴权,并且处理相关的文件或者数据。即使使用 Node 来搭建后端服务,也需要耗费不少的搭基础架构、后期运维的工作量。” [图片] “而小程序·云开发则释放了小程序开发者的手脚,赋予了开发者安全、稳定读取数据、上传文件和控制权限的能力,其它的负载、容灾、监控等,我们小程序开发者只需要关注业务逻辑,专注写好业务逻辑即可,其他的事情完全可以不用操心了!本来我还一筹莫展,了解完’小程序·云开发‘的产品原理以后,我瞬间心里有谱了。” 二维码扫不出来了 [图片] 道路总是不平坦的 ,在腾讯相册小程序通往用户破亿的道路上,困难重重。 由于腾讯相册的二维码需要带上的信息量过大,因此它的二维码显得密密麻麻。这种密集的二维码在某些Android机型下,容易出现无法识别小程序的问题。 这严重制约了腾讯相册小程序分享获客的能力。 [图片] (需要存储name, ownerid, page等大量信息) 这个事情的解决并不难,只需后台开发把数据先存储到数据库中,然后把数据id放到分享链接上,这样,链接便可以转化成32个字符的短链接,让二维码看起来没有那么密集了。 但由于后台人力不足,于是前端开发David利用小程序· 云开发的数据库存储能力,通过调用db.collection(‘qr’).add接口,快速实现数据在数据库中的存储。 [图片] (云开发数据库,格式类似MongoDB) [图片] (云开发数据库索引,可加快数据读取) [图片] 此外,腾讯相册还借住小程序·云开发的云函数能力,生成辨识度更高的小程序码(小程序码文档),用以在朋友圈里传播分享。 [图片] (生成小程序码的云函数逻辑) [图片] (优化后的分享图片和小程序码) 2天上线评论点赞功能 [图片] (评论与点赞功能) 腾讯相册在微信端的核心应用场景是“在微信做分享相册照片”,为了增强腾讯相册用户在微信里的互动,提升用户粘性和留存,腾讯相册决定新增评论与点赞功能,并且把聊天评论就直接在微信聊天窗口里面实现。 在这里,腾讯相册的David面临了两个选择,一是按原开发模式(前台开发-后台开发-前后台联调)做这个功能,面临的问题便是开发周期长、缺后台、迭代速度慢;另一个就是借助云开发的能力,亲自上阵。 为了加快产品迭代速度,David决定采取云开发的开发方式。评论、点赞通过云开发的数据库插入和查询接口,如db.collection(‘comment’).add,很快就实现了。 但遇到棘手的问题是,对于一些敏感的操作比如删除和编辑评论、点赞这些敏感操作,还需要到用户的鉴权操作,而这些鉴权信息,都在原有的后台。此时,云函数的路由功能便发挥出作用了。 [图片] (评论点赞逻辑) 用户进行评论点赞的时候,会在小程序端发起请求调用云函数并带上 [代码]openid[代码],云函数用 [代码]openid[代码] 查询原有的后台服务看看该用户是否有权限进行操作,如果用户具有权限,则把评论和点赞的数据都写入云开发的数据库中。 就这样,借住小程序·云开发的能力,腾讯相册仅用2天时间,完成了在传统开发模式下需要1周多工作量的开发工作。 原有开发模式 云开发全栈开发 工作量 后台1周(微信登录态校验+业务逻辑server开发)+ 前后台联调1天 1 - 2天,无需联调 什么是小程序·云开发? 小程序·云开发是基于腾讯云研发的全新 云开发 Tencent Cloud Base(简称 TCB) 服务,腾讯云与微信力推的这套云开发服务的诞生,恰逢其时地帮助腾讯相册走出开发效率的瓶颈。 [图片] (基于腾讯云的云开发) 小程序云目前提供三大基础能力支持: 云函数:充当了后台的角色,开发者可以在上面用 Node (后续还会支持 PHP, Python 等)写后台逻辑,跟微信私有协议天然鉴权,可以云函数里直接获取 [代码]appid[代码], [代码]openid[代码], [代码]unionid[代码] 等重要鉴权信息,大大简化了小程序后台的开发工作量。 数据库:一个既可在小程序前端操作,也能在云函数中读写的文档型数据库,提供控制台可视化管理 文件存储:在小程序前端和云函数里都可以直接上传/下载云端文件,提供控制台可视化管理 如果你是全新开发的小程序,架构非常轻量简单,如下图。 [图片] 如果你是已有的小程序,部份需要跟原有后台交互的功能,完全可把云函数作为路由,节省获取openid 等用户信息的逻辑,如下图: [图片] 相关资料 小程序·云开发解决方案 小程序·云开发文档
2019-02-19 - 超实用小程序官方能力
小程序管理后台 微信公众平台里,其实藏着一些好用的能力,一起来看看把。 问题定位辅助 运维中心 在小程序管理后台,【开发】-【运维中心】里,可以有以下能力: 错误查询: 可以查到所有小程序运行错误的记录。 性能监控: 可以监控小程序运行的性能,包括不同时间段的[代码]启动耗时[代码]、[代码]下载耗时[代码]、[代码]初次渲染耗时[代码]等。 告警设置: 错误告警通过微信群来通知,每个小程序对应唯一的告警群,扫码加入后即可接收告警通知。 <br> 微信7.0以后: 如果使用工具压缩和编译代码,会自动带上 sourcemap ,运维中心的错误会显示原来文件名字和行号 如果使用第三方框架,在代码中内敛 sourcemap 或者有同名 sourcemap 文件存在,工具会自动合并和解析,从而做到错误会显示原来文件名字和行号。 上传的代码包体积大小,会剔除 sourcemap 文件体积噢,所以大渣可以放心用~~ [图片] <br> 日志管理 开发中日志打印,使用日志管理器实例[代码]LogManager[代码]。使用方式查看API - LogManager。 用户在使用过程中,可以在小程序的 profile 页面,点击【投诉与反馈】-【功能异常】-【勾选上传日志】,则可以上传日志。 <br> [图片] <br> 在小程序管理后台,【管理】-【反馈管理】,就可以查看上传的日志。 <br> [图片] 运营数据 有两种方式可以方便的看到小程序的运营数据: 在小程序管理后台,【统计】,可以看到常规分析(概况、访问分析、来源分析、用户画像)与自定义分析,点击相应的 tab 可以看到相关的数据。 使用小程序数据助手,在微信中方便的查看运营数据。 <br> 常规分析(不需要配置或开发) 参考文档: 常规分析 概况:提供小程序关键指标趋势以及 top 页面访问数据,快速了解小程序发展概况; 访问分析:提供小程序用户访问规模、来源、频次、时长、深度、留存以及页面详情等数据,具体分析用户新增、活跃和留存情况; 实时统计:提供小程序实时访问数据,满足实时监控需求; 用户画像:提供小程序的用户画像数据,包括用户年龄、性别、地区、终端及机型分布。 <br> 似乎晒点图会更加直观: [图片] [图片] [图片] <br> 自定义分析(需自行配置和开发) 参考文档: 自定义分析 配置自定义上报,精细跟踪用户在小程序内的行为,结合用户属性、系统属性、事件属性进行灵活多维的事件分析和漏斗分析,满足小程序的个性化分析需求。 同样,晒点官方图: [图片] [图片] 第三方能力 TGit 代码托管 重要Tips: TGit 能力即将会升级为 git.weixin.qq.com,将全量免费提供给微信的开发者,期待最新消息!~ 更多详情,可以参考TGit开通及配置流程。 Update: 「微信开发者·代码管理」功能上线 TGit 能力已升级为微信开发者·代码管理。 小程序开发工具 调试 真机调试 参考文档:真机调试 我们经常会遇到小程序开发工具没有问题,但是真机上跑的时候就出翔了,或者某些UI歪掉了,这时候真机调试就显得特别方便啦~ 真机远程调试功能可以实现直接利用开发者工具,通过网络连接,对手机上运行的小程序进行调试,帮助开发者更好的定位和查找在手机上出现的问题。 使用方式: 点击开发者工具的工具栏上 “远程调试” 按钮。 实用能力: 调试:断点、单步调试,可在 console 里调试[代码]wx.***[代码]能力 查看和 debug 请求 查看 UI 布局和样式(定位奇葩的手机兼容的时候,特别好用) [图片] 构建 npm 支持 参考文档:npm 支持 小程序基础库版本[代码]2.2.1[代码]开始,就支持 npm 构建啦。 安装 npm 依赖。 点击开发者工具中的菜单栏:【工具】-【构建 npm】,就可以啦。 自定义预处理 通过自定义预处理,我们可以设置在上传代码之前,做一些什么操作,例如跑测试、编译构建等。可以通过[代码]project.config.json[代码]中的[代码]scripts[代码]来配置: [代码]beforeCompile[代码]: 编译前预处理命令 [代码]beforePreview[代码]: 预览前预处理命令 [代码]beforeUpload[代码]: 上传前预处理命令 测试体验 小程序开发助手 参考文档:小程序开发助手 微信公众平台发布的官方小程序,帮助开发和运营人员在手机端更方便快捷地查看和预览小程序。 有权限的项目成员,可以直接点击体验任何一个需要测试或体验的版本,而不需要二维码的口口相传。 最新消息:小程序开发助手已经升级为小程序助手了,在移动端可以管理版本了~ 体验评分 参考文档:体验评分 体验评分是一项给小程序的体验好坏打分的功能,它会在小程序运行过程中实时检查,分析出一些可能导致体验不好的地方,并且定位出哪里有问题,以及给出一些优化建议。 [图片] 手动启动: 在调试器区域切换到 Audits 面板。 点击左上角”开始“按钮,然后自行操作小程序界面,运行过的页面就会被“体验评分”检测到。 点击 “Stop" 停止分析,就会看到一份分析报告,之后便可根据分析报告进行相关优化。 自动运行(实时检查): 开发者在工具的右上角 “详情” 面板里勾选 “自动运行体验评分” 选项即可开启。 参考 小程序调试 结束语 很多时候,我们觉得小程序的开发和调试总是有哪里不满意,但其实官方在很认真地完善各个环节,不过他们比较低调,很多能力我们都没有用上。 很多 web 开发写小程序,都喜欢用像 mpvue、wepy 这些框架。不用去了解小程序的运行机制、底层原理,其实也很方便。不过作为一个细腻的开发,其实了解一下也能发现小程序它棒在哪里了。
2019-02-25 - 实现小程序canvas拖拽功能
组件地址 https://github.com/jasondu/wx-comp-canvas-drag 实现效果 [图片] 如何实现 使用canvas 使用movable-view标签 由于movable-view无法实现旋转,所以选择使用canvas 需要解决的问题 如何将多个元素渲染到canvas上 如何知道手指在元素上、如果多个元素重叠如何知道哪个元素在最上层 如何实现拖拽元素 如何缩放、旋转、删除元素 看起来挺简单的嘛,就把上面这几个问题解决了,就可以实现功能了;接下来我们一一解决。 如何将多个元素渲染到canvas上 定义一个DragGraph类,传入元素的各种属性(坐标、尺寸…)实例化后推入一个渲染数组里,然后再循环这个数组调用实例中的渲染方法,这样就可以把多个元素渲染到canvas上了。 如何知道手指在元素上、如果多个元素重叠如何知道哪个元素在最上层 在DragGraph类中定义了判断点击位置的方法,我们在canvas上绑定touchstart事件,将手指的坐标传入上面的方法,我们就可以知道手指是点击到元素本身,还是删除图标或者变换大小的图标上了,这个方法具体怎么判断后面会讲解。 通过循环渲染数组判断是非点击到哪个元素到,如果点击中了多个元素,也就是多个元素重叠,那第一个元素就是最上层的元素啦。 ###如何实现拖拽元素 通过上面我们可以判断手指是否在元素上,当touchstart事件触发时我们记录当前的手指坐标,当touchmove事件触发时,我们也知道这时的坐标,两个坐标取差值,就可以得出元素位移的距离啦,修改这个元素实例的x和y,再重新循环渲染渲染数组就可以实现拖拽的功能。 如何缩放、旋转、删除元素 这一步相对比较难一点,我会通过示意图跟大家讲解。 我们先讲缩放和旋转 [图片] 通过touchstart和touchmove我们可以获得旋转前的旋转后的坐标,图中的线A为元素的中点和旋转前点的连线;线B为元素中点和旋转后点的连线;我们只需要求A和B两条线的夹角就可以知道元素旋转的角度。缩放尺寸为A和B两条线长度之差。 计算旋转角度的代码如下: [代码]const centerX = (this.x + this.w) / 2; // 中点坐标 const centerY = (this.y + this.h) / 2; // 中点坐标 const diffXBefore = px - centerX; // 旋转前坐标 const diffYBefore = py - centerY; // 旋转前坐标 const diffXAfter = x - centerX; // 旋转后坐标 const diffYAfter = y - centerY; // 旋转后坐标 const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180; const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180; // 旋转的角度 this.rotate = currentGraph.rotate + angleAfter - angleBefore; [代码] 计算缩放尺寸的代码如下: [代码]// 放大 或 缩小 this.x = currentGraph.x - (x - px); this.y = currentGraph.y - (x - px); [代码]
2019-02-20