- 小程序整体流程
设计IdeaFrom 给你1个亿是否是自己熟悉的领域?是否足够的小?功能是否足够的专注?是否有孤注一掷的思想?是否分析过行业的上下游?是否能经过反复的推敲?是否能精确计算出单价、成本、毛利、何时赚钱?是否承认idea产生的过程是不可琢磨的?是否现在是最合适的时机?竞品分析是否有竞品,它的优缺点是什么原型sketch组件库、组件预览PC端适配适老化适配商业模式广告电商会员开发注册申请账号安装开发工具设置团队开发(页面计算工作量)基础:js、css组件使用,较多逻辑开发上架预览上传代码提交审核发布运营目标场景(为哪些人提供哪些连接/服务)指标及定义数据分析留存率、DAU、PV运营招式行业运营平台化:连锁加盟、传统经销渠道供应链:服装定制案例合作伙伴:批发行业案例流量私域流量地推活动支付:收款码案例、优惠券朋友圈广告一物一码微信生态公众号联动群及KOL朋友圈搜一搜+好物圈效果评估
2022-09-11 - slider组件滑块是否可以隐藏?
[图片]想问下这个滑块是否可以隐藏掉
2021-12-14 - 【实战记录】关于实现聊天室语音播放问题的一次曲线救国的记录
需求背景: 该项目为互联网医院小程序,其中重要的功能为在线问诊,需要建立医患聊天的功能,故本项目集成了融云sdk即时通讯单聊功能。 问题: 接了融云的sdk后,发文字,图片及自定义消息都是那么的丝滑,但在上线阶段,发现微信小程序真机中部分语音播放不了;通过分析定位,发现由于 wx.createInnerAudioContext(),部分语音在ios真机中会报INNERERRCODE:-11800, ERRMSG:这项操作无法完成。通过和融云技术一顿沟通,也在查找社区文档后发现,早在2021年3月份,有人也提了这个问题,也没有找到官方的相关反馈,并且我自己提了bug(https://developers.weixin.qq.com/community/develop/doc/0004224a4c09c0553ddd1c09657800),也没有下文了。没办法,项目上线迫在眉睫,只能换其他技术替代方案了 解决方案: 为兼容wx.createInnerAudioContext()报错,在onError时,保存需要通过uni.getBackgroundAudioManager()来播放的音频列表 this.audio = uni.createInnerAudioContext(); this.audio.src = this.src; this.audio.onPlay(() => { this.audio.isPlaying = true; this.animate = true; this.timer = setInterval(() => { this.animate = false setTimeout(() => { this.animate = true }, 50) }, 1250); this.audio.onStop(() => { this.audio.isPlaying = false; this.animate = false; this.timer && clearInterval(this.timer) }) this.audio.onEnded(() => { this.audio.isPlaying = false; this.animate = false; this.timer && clearInterval(this.timer) }) }) this.audio.onError((err) => { this.$bgAudioList.push({ messageUId: this.messageUId, animate: false, }) // 所有需要通过背景音乐播放的存入全局 this.useBackgroundAudioManager = true; }) this.$audioList.push(this.audio)//所有实例加入全局变量 效果如下: [图片]
2022-05-06 - 小程序背景音乐
js: const bgMusic = wx.getBackgroundAudioManager();//背景音频 Page({ /** * 页面的初始数据 */ data: { musicArr:[], // 音乐播放器 bgAudio:{ index:0, isOpen: false,//播放开关 starttime: '00:00', //正在播放时长 duration: '00:00', //总时长 src:"", title:"", }, }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let that=this; that.data.musicArr=[{ id:1, name:"红色基因绿色源泉", src:"/store/activity/audio/mpeg/41FFCFBD5494DFE67FE7E22AC54EEB282021092926031664", duration:"04:20" },{ id:2, name:"我和我的祖国", src:"/store/activity/audio/mpeg/D342C12632FC462D142FEA8A80677D872021092941031658", duration:"03:08" }]; that.setData({ musicArr:that.data.musicArr, }) that.getMusic(0); }, getMusic(index){ this.data.bgAudio.index=index; this.data.bgAudio.duration=this.data.musicArr[index].duration; this.data.bgAudio.src=this.data.musicArr[index].src; this.data.bgAudio.title=this.data.musicArr[index].name; this.setData({ bgAudio:this.data.bgAudio }) }, // 播放 ButtonPlay: function () { // wx.showLoading({ // title: '加载中...', // }) var that = this; //bug ios 播放时必须加title 不然会报错导致音乐不播放 bgMusic.title = that.data.bgAudio.title; bgMusic.epname = that.data.bgAudio.title; bgMusic.src = that.data.bgAudio.src; that.data.bgAudio.isOpen=true; bgMusic.onTimeUpdate(() => { //bgMusic.duration总时长 bgMusic.currentTime当前进度 var duration = bgMusic.duration; var offset = bgMusic.currentTime; var currentTime = parseInt(offset); var min = "0" + parseInt(currentTime / 60); var max = parseInt(duration); var sec = currentTime % 60; if (sec < 10) { sec = "0" + sec; }; var starttime = min + ':' + sec; /* 00:00 */ that.data.bgAudio.offset=currentTime; that.data.bgAudio.starttime=starttime; that.data.bgAudio.max=max; that.setData({ bgAudio:that.data.bgAudio }) console.log("index.js",starttime) }) //播放结束 bgMusic.onEnded(() => { that.data.bgAudio.starttime='00:00'; that.data.bgAudio.isOpen=false; that.data.bgAudio.offset=0; that.setData({ bgAudio:that.data.bgAudio }) bgMusic.stop(); // 循环播放 播放完最后一条循环播放 if(that.data.bgAudio.index { console.log("播放中index.js") that.getHits(that.data.musicArr[that.data.bgAudio.index].id); that.data.bgAudio.isOpen=true; that.setData({ bgAudio:that.data.bgAudio }) }) }, //暂停播放 ButtonPause(){ var that = this bgMusic.pause(); that.data.bgAudio.isOpen=false; that.setData({ bgAudio:that.data.bgAudio }) }, // 进度条拖拽 sliderChangeBj(e) { var that = this var offset = parseInt(e.detail.value); bgMusic.play(); bgMusic.seek(offset); that.data.bgAudio.isOpen=true; that.setData({ bgAudio:that.data.bgAudio }) }, }) wxss: /* 音频播放器 */ .width{ width:92%; margin: auto; } .flex{ display: flex; align-items: center; flex-wrap: nowrap; } .flex1{ flex:1; } .audio-box{ height:190rpx; background: #fff; padding:30rpx; margin-top:30rpx; border-radius: 20rpx; } .audio-left{ width:64rpx; height:64rpx; } .audio-right{ padding-left:30rpx; } .audio-title{ font-size: 30rpx; font-weight: 400; color: #333333; } .audio-box slider{ padding:0; margin:0; margin-top:10rpx; } wx-slider .wx-slider-handle-wrapper{ height:10rpx; border-radius: 10rpx; } .audio-time{ font-size: 24rpx; font-weight: 400; color: #999999; } wxml <!-- 音频播放器 --> <view class="audio-box width flex"> <text catchtap="{{bgAudio.isOpen? 'ButtonPause':'ButtonPlay'}}">{{bgAudio.isOpen? '暂停':'播放'}}</text> <!-- <image class="audio-left" src="{{bgAudio.isOpen? imagesSrc.icon33:imagesSrc.icon13}}" catchtap="{{bgAudio.isOpen? 'ButtonPause':'ButtonPlay'}}"></image> --> <view class="audio-right flex1"> <view class="audio-title line1">{{bgAudio.title}}</view> <slider bindchange="sliderChangeBj" block-size="14" block-color="#E0C999" step="1" value="{{bgAudio.offset}}" max="{{bgAudio.max}}" activeColor="#E0C999" /> <view class="audio-time flex"> <view class="flex1">{{bgAudio.starttime}}</view> <view>{{bgAudio.duration}}</view> </view> </view> </view> 效果图: [图片]
2023-07-03 - 微信小程序动画实现方案
微信小程序动画实现方案 背景 基于商城业务需求背景下,实现加购动画;具体要求如下: 动画落点不准 体验优化,动画先行,每点击一次需要触发一次动画 减少动画掉帧,卡;避免动画延迟,解决加购卡顿问题 基于以上诉求,对微信小程序各种动画实现方案做了简单的对比分析 动画落点不准确 最开始实现方案,就是在dom ready 的时候去获取元素落点,缓存下来,后续复用改落点即可; [代码] onReady() { // 获取落点坐标 this.getBubblePos('#end-point'); }, [代码] 然而在商城当前业务中,这种方案,获取到的坐标频繁出现较大偏差。基于现状,延迟获取落点坐标,出现以下方案(timeout 和 nextTick 都尝试了,依然没能解决问题,于是综合了一下) [代码] onReady() { // 获取落点坐标 this.$nextTick(() => { setTimeout(() => { this.getBubblePos('#end-point'); }, xxxx); }) }, [代码] [代码]timeout[代码]方案尝试了不同的延迟时间,发现在1s内,依旧会出现 获取到的位置不准确,初步判断跟加购动画落点所在的结算条的条件渲染有关。timeout 解决不了问题。 最终在x大佬的指导下,参考了其他平台的实现方案,采取了点击时获取落点坐标,解决了该问题; [代码] async startAnimation(e) { //点击时 获取落点 并 缓存 await this.setStartPos(`#end-point`); // 开始动画 this.useAnimateApi(0); }, [代码] 快速点击下能连续触发,多个动画并存 需要有多个动画元素,保证快速点击的时候,动画不延迟,并且每次点击都能触发一个动画元素执行动画;动画元素要求能购复用,减少冗余dom 元素; 业务现状: 现有加购按钮跟落点所在的结算条位于不同的组件内;点击时获取到点击位置的坐标,然后需要将动画元素从点击位置 移动到落点; 解决方案: 每个页面初始化 [代码]n[代码] (n=10)个元素隐藏在 页面内部 css 隐藏到页面某个区域;为了回收复用动画元素 用一个队列来维护当前可使用动画元素; [代码] <!-- css 元素2个元素实现 --> <block wx:for="{{transition}}" wx:key="id"> <view class="bubblebox bubblebox_{{index}}" style="{{item.parent}}"> <view class="bubble bubble_{{index}}" bind:transitionend="transitionEnd" data-index="{{index}}" style="{{item.child}}"></view> </view> </block> [代码] [代码]// 维护动画元素队列,动画执行完成之后 回收当前动画元素 transitionEnd(e) { console.log(e); // 监听动画结束时间 清除动画 const { index = 0 } = e.currentTarget.dataset || {}; this.data.queue.push(Number(index)); this.data.transition.splice(index, 1, { id: index, parent: '', child: '' }); this.setData({ transition: this.data.transition, }); }, [代码] 掉帧 卡顿问题 针对该问题,对各个实现方案做了个对比 Note: 商城加购动画 最终实现方案采用 css transition 实现 [代码]wx.createAnimation[代码] [代码] useCreateAnimation(index) { const { x: x1, y: y1 } = this.data.startPos; const { x: x2, y: y2 } = this.data.dropPos; // 1. 移动到 起点 // duration 不能为0 最小为1 this.animation.translate(x1, y1).opacity(1).step({ duration: 1, delay: 0 }); // const parent = this.animation.export(); // // 2. 起点移动到 落点 this.animation.translate(x2, y2).step({ duration: 1000, delay: 1 }); // // // 3. 隐藏气泡 回到起点 this.animation.opacity(0).translate(0, 0).step({ duration: 1, delay: 1000 }); const child = this.animation.export(); this.data.transition.splice(index, 1, { id: index, parent: '', child }); this.setData({ transition: this.data.transition, }); }, [代码] [代码]this.animate[代码] 从小程序基础库 2.9.0 开始 [代码] useAnimateApi(index) { const { x: x1, y: y1 } = this.data.startPos; const { x: x2, y: y2 } = this.data.dropPos; console.log(`useAnimateApi`, index, x1, y1, x2, y2); // 2. 上 下 移动动画 this.animate(`#bubble_${index}`, [{ left: `${x1}px`, top: `${y1}px`, opacity: 1 }, { translate: [`${x2 - x1}px`, `${y2 - y1}px`] }], 500, () => { console.log('animationEnd'); this.clearAnimation(`#bubble_${index}`, () => {}); this.animationEnd(index); }); }, [代码] [代码]css3 transition[代码] [代码] useCssAnimate(index) { const { x: x1, y: y1 } = this.data.startPos; const { x: x2, y: y2 } = this.data.dropPos; const parent = `transition: transform 0ms 0ms linear;transform: translate(${x1}px,${y1}px)`; const child = `transition:opacity 0s 0s,transform 2s 0ms linear;transform: translate(${x2 - x1}px,${y2 - y1}px);opacity:1;`; this.data.transition.splice(index, 1, { id: index, parent, child }); this.setData({ transition: this.data.transition, }); }, [代码] [代码]wxs[代码] 相应事件 小程序双引擎设计,setData 是影响性能的关键因素,wxs 相应事件能减少 setData 次数,有助于提高动画性能; [代码]<wxs module="utils"> var transition = function(e, ownerInstance) { var index = 0 var parent = ownerInstance.selectComponent('#bubblebox_1_'+index) // 返回组件的实例 var child = ownerInstance.selectComponent('#bubble_1_'+index) // 返回组件的实例 var x1 = 300 var y1 =50 var x2 = 50 var y2 = 278 console.log(parent,child) parent.setStyle({ transition: 'transform 0ms 0ms linear', transform: 'translate(300px,50px)' }) child.setStyle({ transition:'opacity 0s 0s,transform 2s 0ms linear', transform: 'translate(-250px,238px);opacity:1' }) return false // 不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault } module.exports = { transition:transition } [代码] </details> [代码]css3 animation[代码] 待尝试,动画生成keyframes 如何绑定到 dom 上? [代码]css transition[代码] 细节实现 分析当前动画元素过程 css 元素从隐藏的位置(fixed到左上角,(0,0))移动到起点,并可见; [代码]transition:opacity 0s 0s,top 0s 0s,left 0s 0s,transform 2s 0ms linear; transform: translate(1px,1px);opacity:1; top:100px; left:100px; [代码] 通过top,left 移动元素到起点,并且 通过opacity 控制元素展示出来;执行 translate 动画 利用[代码]transition[代码] 可以同时为不同的 [代码]动画属性[代码] 指定不同的 [代码]duration[代码] [代码]timeFunction[代码] [代码]delay[代码] 动画结束 移除动画元素绑定的 style,动画元素回到起点;动画结束,维护可使用的动画元素队列;监听动画结束事件,回收当前元素入队。 [代码] <view bind:transitionend="transitionEnd"></view> [代码] [代码] transitionEnd(e) { console.log(e); const { index = 0 } = e.currentTarget.dataset || {}; // 动画元素 入队 this.data.queue.push(Number(index)); // 监听动画结束时间 清除动画 this.data.transition.splice(index, 1, { id: index, parent: '', child: '' }); this.setData({ transition: this.data.transition, }); }, [代码] 可以用1层dom,为何用2层dom ? 虽然元素已经脱离了文档流,top left 并不会触发 重绘 引起性能问题;但是不能充分利用css3 硬件加速的能力。 完美解决??? 该方案对 dom性能有较大提高,但是消耗的内存占用也不容小觑。目前商城业务高度复杂,内存占本来就高的情况下,改方案依旧会在内存占用过高的情况下[代码]闪现[代码],过渡态消失的情况。 抛物线方案 该方案 必须使用2层view元素实现 抛物线方案,改方案初步实现后在小程序里面不同机型上 效果差异较大,动画方案回退为直线; 具体步骤: 移动到起点 父级元素 向左边移动 同时子级元素先向上移动指定距离,再向下(时间函数控制曲线斜率) 回收元素 参考: [代码]// 1. 抛物线 const parent = `transition: top 0s 0s, left 0s 0s, opacity 0s 0s, visibility 0s 0s, transform 500ms 0ms ease-in;transform: translateX(${ x2 - x1 }px);left:${x1}px;top:${y1}px;opacity:1;visibility:visible;` const child = `transition: transform 300ms 200ms ease-in, margin-top 200ms 0ms ease-in-out,opacity 0ms 501ms linear,visibility 0ms 501ms linear;margin-top: -66px;transform: translateY(${ y2 - y1 + 66 }px);opacity:0;visibility:hidden;` [代码] Refer weixin miniprogram wxs 响应事件 浏览器的重绘和回流(Repaint & Reflow)
2021-12-13 - 你(可能)不知道的web api
简介 作为前端er,我们的工作与web是分不开的,随着HTML5的日益壮大,浏览器自带的webapi也随着增多。本篇文章主要选取了几个有趣且有用的webapi进行介绍,分别介绍其用法、用处以及浏览器支持度,同时我也分别为这几个api都做了一个简单的demo(真的很简单,样式等于没有~)这几个api分别是: page lifecycle onlineState 利用deviceOrientation制作一个随着手机旋转的正方体 battery status custom event 利用execCommand完成一个简单的富文本 page lifecycle(网页生命周期) 介绍 我们可以用document.visibitilityState来监听网页可见度,是否卸载,但是在手机和电脑上都会现这种情况,就是比如说页面打开过了很久没有打开,这时你看在浏览器的tab页中看着是可以看到内容的,但是点进去却需要加载。chrome68添加了 freeze和 resume事件,来完善的描述一个网页从加载到卸载,包括浏览器停止后台进程,释放资源各种生命阶段。从一个生命周期阶段到另外一个生命周期阶段会触发不同的事件,比如onfocus,onblur,onvisibilitychange,onfreeze等等,通过这些事件我们可以响应网页状态的转换。具体的教程推荐大家看看阮一峰大神的教程。 用法 [代码]window.addEventListener('blur',() => {}) window.addEventListener('visibilitychange',() => { // 通过这个方法来获取当前标签页在浏览器中的激活状态。 switch(document.visibilityState){ case'prerender': // 网页预渲染 但内容不可见 case'hidden': // 内容不可见 处于后台状态,最小化,或者锁屏状态 case'visible': // 内容可见 case'unloaded': // 文档被卸载 } }); [代码] 用处 大家可以看下这个demo [图片] 所以说,这个API的用处就是用来响应我们网页的状态,比如说我们的页面是在播放视频或者是一个网页的游戏,你可以通过这个API来去做出对应的响应,暂停视频,游戏暂停等等。 浏览器支持度 page visibilituState [图片] online state(网络状态) 这个API就很简单了,就是获取当前的网络状态,同时也有对应的事件去响应网络状态的变化。 用法 [代码]window.addEventListener('online',onlineHandler) window.addEventListener('offline',offlineHandler) [代码] 用处 比如说我们的网站是视频网站,正在播放的时候,网络中断了,我们可以通过这个API去响应,给用户相应的提示等等。 浏览器支持度 [图片] Vibration(震动) 让手机震动~~~ 嗯,就这么简单。 用法 [代码]// 可以传入一个大于0的数字,表示让手机震动相应的时间长度,单位为ms navigator.vibrate(100) // 也可以传入一个包含数字的数组,比如下面这样就是代表震动300ms,暂停200ms,震动100ms,暂停400ms,震动100ms navigator.vibrate([300,200,100,400,100]) // 也可以传入0或者一个全是0的数组,表示暂停震动 navigator.vibrate(0) [代码] 用处 用来给用户一个提示,比如说数据校验失败,当然震动不止这点作用,大家自己去扩展吧~~~ 浏览器支持度 [图片] device orientation(陀螺仪) 通过绑定事件来获取设备的物理朝向,可以获取到三个数值,分别是: alpha:设备沿着Z轴的旋转角度 [图片] beta:设备沿着X轴的旋转角度 [图片] gamma:设备沿着Y轴的旋转角度 [图片] 用法 [代码]window.addEventListener('deviceorientation',e => { console.log('Gamma:',e.gamma); console.log('Beta:',e.beta); console.log('Alpha:',e.Alpha); }) [代码] 用处 这种自然是web VR 中的使用场景会相对较多。这是我写的一个小demo [图片] 浏览器支持度 [图片] battery status 这个API就使用来获取当前的电池状态 用法 [代码]// 首先去判断当前浏览器是否支持此API if ('getBattery' in navigator) { // 通过这个方法来获取battery对象 navigator.getBattery().then(battery => { // battery 对象包括中含有四个属性 // charging 是否在充电 // level 剩余电量 // chargingTime 充满电所需事件 // dischargingTime 当前电量可使用时间 const { charging, level, chargingTime, dischargingTime } = battery; // 同时可以给当前battery对象添加事件 对应的分别时充电状态变化 和 电量变化 battery.onchargingchange = ev => { const { currentTarget } = ev; const { charging } = currentTarget; }; battery.onlevelchange = ev => { const { currentTarget } = ev; const { level } = ev; } }) } else { alert('当前浏览器不支持~~~') } [代码] 用处 用来温馨的提示用户当前电量~~~ 浏览器支持度 这个浏览器的支持度很低。。。。 [图片] execCommand 执行命令 当将HTML文档切换成设计模式时,就会暴露出 execcommand 方法,然后我们可以通过使用这个方法来执行一些命令,比如复制,剪切,修改选中文字粗体、斜体、背景色、颜色,缩进,插入图片等等等等。 用法 用法也很简单,这里简单介绍几个,详细的介绍大家可以去MDN上看看。 这个API接受三个参数,第一个是要执行的命令,第二个参数mdn上说是Boolean用来表示是否展现用户界面,但我也没测试出来有什么不同,第三个参数就是使用对应命令所需要传递的参数。 [代码]// 一般不会直接去操作我们本身的HTML文档,可以去插入一个iframe然后通过contentDocument来获取、操作iframe中的HTML文档。 let iframe = document.createElement('ifram'); let doc = iframe.contentDocument; // 首先要将HTML文档切换成设计模式 doc.designMode = 'on'; // 然后就可以使用execCommand 这个命令了; // 执行复制命令,复制选中区域 doc.execCommand('copy') // 剪切选中区域 doc.execCommand('cut') // 全选 doc.execCommand('selectAll') // 将选中文字变成粗体,同时接下来输入的文字也会成为粗体, doc.execCommand('bold') // 将选中文字变成斜体,同时接下来输入的文字也会成为斜体, doc.execCommand('italic') // 设置背景颜色,,比如设置背景色为红色,就传入 'red'即可 doc.execCommand('backColor',true,'red') [代码] 用处 我用这些命令简单的写了一个富文本的demo,大家可以看一下Demo,效果如下: [图片] 浏览器支持度 CustomEvent (自定义事件) 大家都知道各种事件是如何绑定的,但是有时候这些事件不够用呢,custom event就可以解决这样的问题。 用法 [代码]let dom = document.querySelector('#app'); // 绑定事件, 传递过来的值可以通过ev.detail 来获取 dom.addEventListener('log-in',(ev) => { const { detail } = ev; console.log(detail); // hello }) // 派发事件,需要传入两个参数,一个是事件类型,另外一个是一个对象,detail就是传递过去的值 dom.dispatchEvent(new CustomEvent('log-in',{ detail:'hello' })) [代码] 用处 绑定自定义事件,最近很火的框架Omi,其中的自定义事件就是基于customEvent实现的。 浏览器支持度 [图片] 最后 就先介绍到这些,web api越来越多,当然每个人不可能全都熟记于心,这篇文章也只是简单介绍一下,还有很多有意思而且很重要的API,比如:web components, service worker,genric sensor等等,不过这些都有很多人在钻研,同时文档相对较多。 相信你看完这些至少已经知道这些API的大概用法了,如果有兴趣了解用法的话,可以去看下我写的demo,也可以去看看MDN文档去深入研究一下。 参考 MDN文档 阮一峰大神的博客 web-api-you-dont-know 视频演讲 http://www.zhangyunling.com/725.html Omi WeElement源码
2019-03-01 - 你不知道的小程序系列之生命周期执行顺序
再次开始之前先问几个问题: 你是否知道[代码]Page[代码]生命周期 与 [代码]pagelifetimes[代码] 生命周期执行顺序? 你是否知道[代码]behaviors[代码]中的生命周期与组件生命周期执行顺序? 你是否知道[代码]Page[代码]生命周期 与 组件[代码]pagelifetimes[代码]生命周期执行顺序? 要回答上面的问题,首先我们看看小程序生命周期有哪些: App onLaunch onShow onHide Page onLoad onShow onReady onHide onUnload Component created attached ready moved detached 想一下加载一个页面(包含组件)的加载顺序,按照直觉小程序加载顺序应该是这样的加载顺序(以下列子中[代码]Component[代码]都是同步组件): App(onLaunch) -> Page(onLoad) -> Component(created) 但其实并不然,小程序的加载顺序是这样的: 首先执行 [代码]App.onLaunch[代码] -> [代码]App.onShow[代码] 其次执行 [代码]Component.created[代码] -> [代码]Component.attached[代码] 再执行 [代码]Page.onLoad[代码] -> [代码]Page.onShow[代码] 最后 执行 [代码]Component.ready[代码] -> [代码]Page.onReady[代码] 其实也不难理解微信这么设计背后的逻辑,我们先看下官方的的生命周期: [图片] 可以看到,在页面[代码]onLoad[代码]之前会有页面[代码]create[代码]阶段,这其中就包含了组件的初始化,等组件初始化完成之后,才会执行页面的[代码]onLoad[代码], 之后页面[代码]ready[代码]事件也是在组件[代码]ready[代码]之后才触发的。 下面我们来看看 [代码]Behavior[代码], [代码]Behavior[代码] 与 [代码]Vue[代码]中的 [代码]mixin[代码] 类似,猜想下其中的执行顺序: Behavior.created => Component.created 测试下来和预期相符,其实在[代码]Vue[代码]的文档中有一段这样的描述: 另外,混入对象的钩子将在组件自身钩子之前调用。 这样的设计和主流设计保持一致。接下来我们看看 [代码]pageLifetimes[代码],有[代码]show[代码]和[代码]hide[代码]生命周期对应页面的展示与隐藏,预期的执行顺序: pageLifetime.show => Page.onShow 测试下来也和预期相符,那么我们可以推断出如下的结论: 当页面中包含组件时,组件的生命周期(包括pageLifetimes)总是优先于页面,[代码]Behaviors[代码]生命周期优先于组件的生命周期。但其实有个例外:页面退出堆栈,当页面[代码]unload[代码]时会执行如下顺序: Page.onUnload => Component.detached 看了以上的分析你应该知道了答案,最后做个总结(demo): [图片] 最后的最后布置个作业 异步组件(异步渲染的组件,通常是通过if条件判断是否渲染)的生命周期执行顺序是怎样的,pagelifetimes会不会执行?
2020-01-10 - 线上小程序userNickName和userAvatarUrl都拿不到,云函数中openId拿不到?
线上小程序问题: [图片] [图片] 本地开发环境好着 [图片] 线上云函数调用日志0更新,没操作数据库 [图片] 本地调用完全ok [图片] 线上和本地前端代码、云函数全都一模一样
2022-02-22 - 关于微信安卓端网页字体适配的通知
为了提供给用户更好的阅读体验,微信安卓版 7.0.10 版本起,网页的字体会跟随微信设置里的字体大小更改而变化。 若调整字体变大或变小后,部分未适配网页的排版会出现显示错乱,建议未进行适配的开发者尽快完成对“ 字体大小” 的适配。 查看网页在字体不同大小下展示效果的方法: 方法1:"设置">"通用">“字体大小">进行字体大小修改后查看对应网页显示效果。 方法2:在微信内访问对应网页右上角”…">底部菜单栏选择调整字体">进行字体大小修改后查看对应网页显示效果。 另外,对于现有的显示问题,我们提供以下方案让开发者临时将字体还原标准大小。同时,开发者可以在页面中提示用户在右上角”…”更多菜单中修改字体到合适的大小。 下列方案可以将字体还原标准大小,但我们仍然建议后续做字体适配来提高用户的阅读体验。 『字体还原标准大小』方案: 我们提供了一个 JSAPI 用于设置字体大小,只需将字体大小等级设置为 2 (标准)即可,代码示例如下: document.addEventListener("WeixinJSBridgeReady", function () { WeixinJSBridge.invoke("setFontSizeCallback", { fontSize: '2' }); }, false); 此外,若页面是用 rem 单位进行排版的(目前该做法更容易导致页面不可用),可以反向重置 font-size 的数值达到还原字体标准大小的目的,此方法在效果上也比较理想。代码示例如下: // 以下代码思路来源网络。同时代码放在 body 标签开头位置效果最佳 var $dom = document.createElement('div'); $dom.style = 'font-size: 10px'; document.body.appendChild($dom); // 计算出放大后的字体 var scaledFontSize = parseInt(window.getComputedStyle($dom, null).getPropertyValue('font-size')); document.body.appendChild($dom); // 计算原字体和放大后字体的比例 var scaleFactor = 10 / scaledFontSize; // 取 html 元素的字体大小 var originRootFontSize = parseInt(window.getComputedStyle(document.documentElement, null).getPropertyValue('font-size')); // 由于设置 font-size 后实际会变大,故 font-size 需设置为更小一级 document.documentElement.style.fontSize = originRootFontSize * scaleFactor * scaleFactor + 'px';
2020-01-14 - 微信小程序性能优化入门指南(转载)
原文地址:https://segmentfault.com/a/1190000016901634 小程序从发布到现在也已经有将近两年的时间,越来越来多的公司开始重视小程序生态带来的流量,今年也由于小程序平台对外能力的越来越多的开放以及小程序平台的自身优化,越来越多的开发者也自主的投入到小程序的开发当中,现在,作为前端如果会写小程序,绝对是一个不折不扣的面试加分项。 相信不少人刚接触小程序时的感觉大都是小程序很简单,开发只要是会写html、css、js就可以了,但是当自己的第一个小程序开发完成上线时,却发现小程序体验非常糟糕,接下来就让我们一窥小程序优化之道。 加载流程要想给小程序做优化,对小程序的加载流程一定要有一定的了解,小程序是怎么加载的,让我们先来看一个图片: [图片] 这三个图片大家一定都不陌生,当你打开一个小程序的时候就会经历这三个过程: 资源准备,这个过程就是小程序在下载你的代码包的过程业务代码注入和渲染,这个过程就是小程序将的业务代码分别注入视图层和逻辑层,并在视图层做视图的渲染异步数据的请求,显示加载中的时候,其实就是在到达首页时,如果首页有异步数据请求,这个时候小程序就会执行异步数据请求上述就是对小程序的启动过程的一个简单概述,让我们再来看一个更加具体一点的图片,可能会更好理解小程序启动过程: [图片] 从这个图片可以看到,小程序在启动加载的时候,其实分为两部分,一部分是逻辑层的启动启动,另一部分是视图层的启动,逻辑层的启动就是加载小程序的js代码,视图层的启动webview对页面进行加载和渲染,那预加载又是什么时候执行的呢?其实在微信动的时候,小程序平台就开始静默执行与加载的过程,包括JS引擎初始化和WebView的初始化,然后会注入小程序自带的公共库,例如自带api、组件等,后面的小程序启动,就是上面说过的打开一个小程序具体的启动加载过程了,下载代码包,分别注入逻辑层和视图层,然后共同完成首屏渲染。 启动性能优化讲完小程序的启动过程,就可以开始介绍具体的性能优化方案了,让我们一起看看影响小程序性能的因素以及具体的解决办法 代码包大小代码包大小会直接影响小程序的启动速度,代码包越大,小程序的启动时间就越长,在小程序启动时,下载代码包和代码注入的时间和小程序代码包大小是成正比的,一般小程序的平均启动时间是2s左右,可以看看你的小程序有没有拖后腿,那么如何控制包大小呢? 资源控制开启开发工具”上传代码时自动压缩”,小程序开发工具有一个上传代码时自动压缩的功能,当开启时,会在你上传代码时为你做代码压缩,除了这个,我们也可以通过使用第三方打包工具做代码压缩,如webpack、grunt、grulp。及时清理无用代码和资源文件,无用的代码和资源也会占用一定的包大小。减少代码包中的资源文件,将资源存放在cdn上,小程序开发工具对资源文件的压缩比率非常低,资源有条件的可以尽量放在CDN上,因为小程序开发工具对资源文件的压缩比率非常低,只有10%左右,或者也可以用第三编译工具对资源文件自己进行压缩处理分包加载[图片] 分包加载是小程序提高加载启动性能的一个重要方法,如果有人还不了解,可以点开链接看官方介绍,那么如何做好分包加载呢? 将小程序中不常用的代码放在分包中,主包内只保留用户最常访问的页面,但是由于官方规定tab页面只能放在主包中,因为小程序启动时只会加载主包,使用时按需下载分包,不会在加载时一次将整个代码包下载,这样就能有效减少启动加载的时间。 但是分包加载也有它的局限性,用户首次打开分包页面时,需要先进行分包代码的加载和注入,会造成页面切换时产生一定的延时,因此在此基础上,官方又推出了分包预加载和独立分包。 分包预加载先来看一下之前分包加载时的流程是怎样的: [图片] 那么分包预加载是怎么干的呢?分包预下载:提前配置可能会跳到哪些分包,框架在进入页面后根据配置进行预下载,分包预加载会在你进入主包页面后,为你静默开启分包代码的下载和注入,这个过程是无感的,来看一下分包预加载的流程是怎样的: [图片] 分包预加载需要注意的是:同一个分包中的页面享有共同的预下载大小限额2M,限额会在工具中打包时校验,因此不能把所有的分包页面都配置到分包预加载的配置中,只配置主包页面会跳转的页面即可。 独立分包独立分包又是什么呢?由于从分包页面启动是,必须要依赖于主包的下载和注入,启动速度会受到主包大小的制约,因此这就有了独立分包,独立分包在启动分包页面时,可以独立启动而不需要依赖主包,这样就可以减少主包下载和注入的时间,通常情况下我们会将活动、广告一类的具有独立逻辑的功能代码标记为一个独立分包,在分包页面启动时,可以不依赖于主包启动,只下载分包代码进行注入。让我们来看一下独立分包的加载流程是怎样的: [图片] 首屏加载性能优化首屏加载的体验对小程序来说十分重要,那么如何提升首屏加载性能呢? 提前请求:异步数据数据请求不需要等待页面渲染完成利用缓存:利用storage API对异步请求数据进行缓存,二次启动时先利用缓存数据渲染页面,再进行后台更新避免白屏:先展示页面骨架和基础内容及时反馈:及时地对需要用户等待的交互操作给出反馈,避免用户以为小程序没有响应渲染性能优化要想提高渲染性能,就需要知道小程序如何做页面渲染的,让我们先来看一个页面渲染的流程图: [图片] js引擎和native都可以过js的计算或者data修改来对Webview发起绘制操作,但是对开发者来说最重要的就是js引擎和Webview之间的通信,这通信过程是一个跨进程通信,是非常耗时的一个过程,我们要提高渲染的性能,也就是减少这个跨进程通信的时间,那么怎么去减少跨进程通信的时间呢? 避免不当使用setData使用data在方法间共享数据,会增加setData传输的数据量,同时会增加页面重绘的概率data仅包括与页面相关的数据使用setData传输大量数据,通讯耗时与数据量正相关,页面更新延迟可能造成更新开销增加仅传输页面中发生变化的数据,使用setData的特殊key实现局部更新后台页面进行setData抢占前台页面的资源页面切入后台后的setData调用,延迟到页面重新展示的时候执行总结来说就是在data中只定义与页面渲染相关的数据,其他与页面渲染无关的数据都定义成普通变量,在做setData操作时,尽量只传输页面渲染需要的数据,当页面切换时,将后台执行的setData操作销毁,等到页面重新展示的时候再执行。 避免不当使用onPageScroll只在必要的时候监听pageScroll事件避免在onPageScroll中执行复杂逻辑避免在onPageSroll中频繁调用setData避免频繁查询节点信息(SelectQuery),部分场景使用节点布局相交状态监听(IntersectionObserver)替代由于onPageSroll事件监听在处理js引擎和webview之间的通信时也是一个跨进程通信,因此在使用onPageScroll事件时,要注意以上的几点内容,来进行相关的优化 使用自定义事件在需要频繁更新的场景下,自定组件的更新只在组件内部更新,不受页面其他部分内容复杂性影响,这样也可以在一定程度优化渲染性能 总结这篇文章简单的介绍了微信小程序性能优化的一些方法,还有很多我没有介绍到方法就需要大家自己去探索总结了。希望大家看完这篇文章能对小程序性能优化有一定的认识,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏。
2021-09-23 - 动态预加载 swiper-item 中的图片(延迟加载)
在使用 swiper 时,有些场景需要一次载入的图片太多,消耗资源的同时也会影响用户体验。 [图片] 自己的解决方法是这样的: 创建图片【地址数组】根据需要展示图片的总数创建一个空的数组,即用来页面绑定的【页面数组】初始化【页面数组】,为第 1、2 项赋值当向右滑动时,接着为【页面数组】的第 3 项赋值直到全部展示完毕[图片] 如果用户提前离开,就能避免未展示项图片的加载。 代码如下: <view class="s-page"> <swiper class="s-swiper" snap-to-edge="false" indicator-dots="true" bindchange="swiperChange"> <block wx:for="{{pageList}}" wx:key="*this"> <swiper-item class="s-swiper_item"> <view>{{item}}</view> </swiper-item> </block> </swiper> </view> Page({ /** * 页面的初始数据 */ data: { imageUrlList: ['url 1', 'url 2', 'url 3', 'url 4', 'url 5', 'url 6', 'url 7'], pageList: [], currentPageIndex: 0 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { // 根据图片总数,创建需要渲染的空数组 let pageList = new Array(this.data.imageUrlList.length).fill(''); // 初始化渲染数组,载入第1张,并且预载入下一张 pageList[this.data.currentPageIndex] = this.data.imageUrlList[this.data.currentPageIndex]; pageList[this.data.currentPageIndex + 1] = this.data.imageUrlList[this.data.currentPageIndex + 1]; // 渲染页面 this.setData({ pageList: pageList }) console.log(this.data.pageList); }, swiperChange: function (e) { let oldPageIndex = this.data.currentPageIndex, // 之前展示的页面索引 newPageIndex = e.detail.current; // 滑动后新展示的页面索引 // 判断是否由用户触摸引起的 if (e.detail.source == 'touch') { console.log(newPageIndex); // 判断滑动方向 if (oldPageIndex < newPageIndex) { console.log('向右滑动'); // 判断是否到最后一张,并且地址信息是否为空 if (newPageIndex < this.data.imageUrlList.length - 1 && !this.data.pageList[newPageIndex + 1]) { this.data.pageList[newPageIndex + 1] = this.data.imageUrlList[newPageIndex + 1] console.log('预加载成功'); } // 渲染页面 this.setData({ currentPageIndex: newPageIndex, pageList: this.data.pageList }) } else if (oldPageIndex > newPageIndex) { console.log('向左滑动'); this.data.currentPageIndex = newPageIndex; } } console.log(this.data.pageList); } }) 如果不显示“面板指示点”,那么【页面数组】直接创建一个空数组即可。 自己的笨办法,如果有更好的方法,请不吝赐教!
2021-07-07 - 官方组件默认样式修改的对应class
swiper面板指示点: .wx-swiper-dots.wx-swiper-dots-horizontal{ margin:20px //调整位置 } .wx-swiper-dot{}//指示点未选中样式 .wx-swiper-dot-active{}//指示点选中样式 button去除边框: button::after { border: none; } checkbox.wx-checkbox-input{}//未选中的框 .wx-checkbox-input.wx-checkbox-input-checked{}//选中的框 .wx-checkbox-input.wx-checkbox-input-checked::before{}//选中的框里的小勾 radio.wx-radio-input{}// 未选中 .wx-radio-input.wx-radio-input-checked{}//选中 .wx-radio-input.wx-radio-input-checked::before{}//选中的小勾 progress.wx-progress-inner-bar{}//已选择的进度条 switch.wx-switch-input{}//
2021-10-29 - 用 wx.getFileSystemManager().copyFile({ 读取文件失败
[图片] 想小程序读取txt文档内的内容 一直报读取失败[图片]
2020-05-26 - 小程序原生高颜值组件库--ColorUI
[图片] 简介 ColorUI是一个Css类的UI组件库!不是一个Js框架。相比于同类小程序组件库,ColorUI更注重于视觉交互! 浏览GitHub:https://github.com/weilanwl/ColorUI [图片] 如何使用? 先下载源码包 → Github 引入到我的小程序 将 /demo/ 下的 colorui.wxss 和 icon.wxss 复制到小程序的根目录下 在 app.wxss 引入两个文件 [代码]@import "icon.wxss"; @import "colorui.wxss"; [代码] 使用模板全新开发 复制 /template/ 文件夹并重命名为你的项目,微信开发者工具导入为小程序就可以使用ColorUI了 体验沉浸式导航 [图片] App.js 获取系统参数并写入全局参数。 [代码]//App.js App({ onLaunch: function() { wx.getSystemInfo({ success: e => { this.globalData.StatusBar = e.statusBarHeight; let custom = wx.getMenuButtonBoundingClientRect(); this.globalData.Custom = custom; this.globalData.CustomBar = custom.bottom + custom.top - e.statusBarHeight; } }) } }) [代码] Page.js 页面配置获取全局参数。 [代码]//Page.js const app = getApp() Page({ data: { StatusBar: app.globalData.StatusBar, CustomBar: app.globalData.CustomBar, Custom: app.globalData.Custom } }) [代码] Page.wxml 页面构造导航。更多导航样式请下载Demo查阅 操作条组件。 [代码]<view class="cu-custom" style="height:{{CustomBar}}px;"> <view class="cu-bar fixed bg-gradual-pink" style="height:{{CustomBar}}px;padding-top:{{StatusBar}}px;"> <navigator class='action border-custom' open-type="navigateBack" delta="1" hover-class="none" style='width:{{Custom.width}}px;height:{{Custom.height}}px;margin-left:calc(750rpx - {{Custom.right}}px)'> <text class='icon-back'></text> <text class='icon-homefill'></text> </navigator> <view class='content' style='top:{{StatusBar}}px;'>操作条</view> </view> </view> [代码] 自定义系统Tabbar [图片] 按照官方 自定义 tabBar 配置好Tabbar (开发工具和版本库请使用最新版)。 使用ColorUI配置Tabbar只需要更改 Wxml 页的内容即可。 更多Tabbar样式请下载Demo查阅 操作条组件。 /custom-tab-bar/index.wxml [代码] <view class="cu-bar tabbar bg-white shadow"> <view class="action" wx:for="{{list}}" wx:key="index" data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab"> <view class='icon-cu-image'> <image src='{{selected === index ? item.selectedIconPath : item.iconPath}}' class='{{selected === index ? "animation" : "animation"}}'></image> </view> <view class='{{selected === index ? "text-green" : "text-gray"}}'>{{item.text}}</view> </view> </view> [代码] 作者叨叨 ColorUI是一个高度自定义的Css样式库,包含了开发常用的元素和组件,元素组件之间也能相互嵌套使用。我也会不定期更新一些扩展到源码。 其实大家都在催我写文档,但这个库源码就在这,所见即所得,粘贴复制就可以得到你想要的页面。当然,文档我还是要写的,也希望大家多多提意见。 现在前端的开发方向基本都是奔着Js方向的,布局和样式大家讨论的有点少。以后我会在开发者社区多聊一聊关于开发中的布局和样式。 [图片] 感谢阅读。
2019-02-26