炎炎夏日,剧烈的高温也抵挡不住对学习的坚持。对于学习,不少时候都会有松懈和逃避的心理,但看到身边近乎疯狂的考研党们,我仍是选择了坚持。距离上次发文章已经有一个月之久了,期间学习了不少知识,越学习愈加以为本身的路还有很长,但既然选择了远方,便只顾风雨兼程了。css
mpvue 是一个使用 Vue.js 开发小程序的前端框架。框架基于 Vue.js 核心,mpvue 修改了 Vue.js 的 runtime 和 compiler 实现,使其能够运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验。前端
关于模拟数据这里提一个网易云音乐api,真的很强但因为小程序不支持本地域名的request请求,须要部署到服务器上变为小程序合法域名,因为精力有限这里只是截取了部分数据进行模拟。有兴趣的小伙伴能够本身玩玩啊。vue
写这个项目的时候,碰到了不少问题,BUG更是很多,在mpvue与原生小程序中疯狂纠结,因为时间精力有限,目前只完成了三个页面,重点把核心播放功能完成了大半。因为网上关于mpvue的资源不多,遇到问题都是在疯狂百度,尝试等等方法也算是解决了部分。这里分享我遇到的一些困难,但愿能帮助到有须要的人。git
npm install flyio
es6
import Vue from 'vue' var Fly = require('flyio/dist/npm/wx.js') //wx.js为flyio的微信小程序入口文件 var fly = new Fly(); fly.interceptors.request.use((config,promise)=>{ config.headers["X-Tag"]="flyio"; //给全部请求添加自定义header return config; }) //配置请求基地址 fly.config.baseURL="https://www.easy-mock.com/mock/5b372361808a747e8d04a1e3/" Vue.prototype.$http=fly //将fly挂载在vue上供全局使用 export default fly
npm install vuex
github
在通常的vue-cli + vuex项目中,主函数 main.js 中会将 store 对象提供给 “store” 选项,这样能够把 store 对象的实例注入全部的子组件中,从而在子组件中能够用this.$store.state.xxx
,this.$store.dispatch
等来访问或操纵数据仓库中的数据web
new Vue({ el: '#app', store, router, template: '<App/>', components: { App } })
注意了,在mpvue + vuex项目中,很遗憾不能经过上面那种方式来将store对象实例注入到每一个子组件中(至少我尝试N种配置不行),也就是说,在子组件中不能使用this.$store.xxx
,从而致使辅助函数不能正确使用。这个时候咱们就须要换个思路去实现,要在每一个子组件中可以访问this.$store
才行。既然咱们须要在子组件中用this.$store
访问store实例,那咱们直接在vue的原型上添加$store属性指向store对象不就行啦,因而解决方案以下:vuex
Vue.prototype.$store = store
vue-cli
src下新建一个store文件夹,目录结构以下npm
action.js //提交mutation以达到委婉地修改state状态,可异步操做 getters.js //获取store内的状态 index.js //引入vuex,设置state状态数据,引入getter、mutation和action mutation-type.js //将方法与方法名分开便于查看 mutations.js //更改store中状态用的函数的存储之地 state.js //存放state
这里展现下index.js内容
import Vue from 'vue' import Vuex from 'vuex' import * as actions from './actions' import * as getters from './getters' import state from './state' import mutations from './mutations' Vue.use(Vuex) export default new Vuex.Store({ actions, getters, state, mutations })
在fly请求数据后咱们能够将其数据存储到state里 方便其余页面或组件使用、修改,在本项目中请求数据能够数据存储下来,后续其它页面须要此数据时能够不用再次请求直接引入调用便可,大大减小了数据请求次数
methods: { ...mapMutations({ //es6 展开运算符 saveDetailState: 'SAVE_DETAIL_STATE', saveMidimg2: 'SAVE_MIDIMG2', saveMidimg3: 'SAVE_MIDIMG3' }), fly .get('music#!method=get') .then(res => { this.menu = res.data.data.menu; this.midimg = res.data.data.midimg; this.saveMidimg2(res.data.data.midimg2); //子组件标题数据 this.saveDetailState(res.data.data.footimg);//推荐歌单信息 this.saveMidimg3(res.data.data.midimg3);// this.footimg2 = res.data.data.footimg2; this.footimg3 = res.data.data.footimg3; })
因为本人开发项目使用的css预编译器stylus 他会自动补全前缀,而小程序又并不支持-moz- -webkit-等前缀那怎么办呢? 解决方案以下:
<style lang="stylus"> ... </style> <style> //不在stylus编译下 @keyframes rotate { 0%{ transform: rotate(0);} 100% { transform: rotate(360deg); } } </style>
这里我使用的是wx.createInnerAudioContext()api 是wx.createAudioContext 升级版。
因为初次使用audio并非很熟悉,碰到了一个坑。播放音乐后返回歌单播放另外一首歌曲,上一首歌曲不会被替换,两首歌竟同时播放,建立audio对象后要结束这个实例须要进行销毁,注意这里咱们须要将这个audio对象进行存储否则再次跳转播放页面找不到销毁的对象
mounted(){ wx.showLoading({ title: '加载中' }) this.midshow = true let options = this.$root.$mp.query; const songid = options.songid; const id = options.id; if(this.audioCtx != null) { //第一次不用销毁 if(this.song.id != songid){ this.audioCtx.destroy()} //销毁实例 } if(this.song.id != songid || this.song.id == '') //同一首歌不用新建 { this.audioCtx = wx.createInnerAudioContext() //新建实例 } if(options.name == 'undefined' && this.song.id != songid){ // 同一首歌不用从新更新数据 this.getSong(songid,id) //获取当前音乐信息 const afterlyric = this.normalizeLyric(this.song.lyric) this.currentLyric = new Lyric(afterlyric) ...
这里还碰到一个并非很理解的地方,就是传入歌曲连接src 后获取总时长并无获取 猜想是引入src获取歌曲信息须要时间? 这里用了很粗暴的方法解决了,应该是使用promise进行异步处理?期待你们能给个指导意见
this.audioCtx.src = this.song.mp3url //设置src this.allmiao = this.audioCtx.duration //读取歌曲总时长 const a = setInterval(()=>{ this.allmiao = this.audioCtx.duration },50) if(this.allmiao){ clearInterval('a') }
copmputed: { width () { return 'width:'+(this.nowmiao/this.allmiao)*550+'rpx' //盒子宽度总为550rpx }, midwidth () { return 'transform:translate3d('+(this.nowmiao/this.allmiao)*529+'rpx,0px,0px);' }}
clClick (e) { //点击进度条事件 const rect = wx.getSystemInfoSync().windowWidth //屏幕总宽 this.offsetWidth = e.pageX - (rect-275)/2 //点击进度条上距离左侧宽度 this.nowmiao = this.allmiao*(this.offsetWidth/275) //根据比例计算点击位置对应的播放时间 const miao = Math.floor(this.nowmiao) this.audioCtx.seek(miao) // 跳转播放 }, // 滑动进度条 分为start、move、end事件 这个你们应该不陌生,思路都是根据距离计算比例 跳转时间点播放 clstart (e) { this.touch.initiated = true const rect = wx.getSystemInfoSync().windowWidth this.touch.setWidth = (rect-275)/2 this.touch.startX = e.touches[0].pageX this.touch.time = this.nowmiao }, clmove (e) { //这里要注意进度条临界点 滑动距离超出的问题 if(!this.touch.initiated) return; const movex = e.touches[0].pageX - this.touch.startX if(e.touches[0].pageX>=(this.touch.setWidth+275)) {this.nowmiao =this.allmiao } else if(e.touches[0].pageX<=this.touch.setWidth) {this.nowmiao = 0;} else {this.nowmiao = this.touch.time+this.allmiao*movex/275} }, clend (e) { this.touch.initiated = false const miao = Math.floor(this.nowmiao) this.audioCtx.seek(miao) },
这里使用了黄轶老师写的一个lyric-parser使用他对歌词数据进行处理等,经过sroll-view-into进行跟随播放进度的滚屏
<scroll-view class="lyric-wrapper" :scroll-into-view="'line'+toLineNum" scroll-y scroll-with-animation> <view v-if="currentLyric"> <view :id="'line'+i" class="text" :class="[currentLineNum == i ? 'current': '' ]" v-for="(item,i) of currentLyric.lines" :key="i"> {{item.txt}} </view> </view> <view v-if="!currentLyric"> <view class="text current">暂无歌词</view> </view> </scroll-view>
将歌词进行处理后v-for输出 经过id、class分别进行滚屏与当前播放歌词行的高亮
normalizeLyric: function (lyric) {//将歌词数据进行拆分 return lyric.replace(/:/g, ':').replace(/ /g, '\n').replace(/./g, '.').replace(/ /g, ' ').replace(/-/g, '-').replace(/(/g, '(').replace(/)/g, ')') }, const afterlyric = this.normalizeLyric(this.song.lyric) this.currentLyric = new Lyric(afterlyric) //建立Lyric实例 this.audioCtx.onTimeUpdate(()=>{ //音频播放进度更新事件 this.nowmiao = this.audioCtx.currentTime //当前播放时间 if (this.currentLyric) { this.handleLyric(this.nowmiao * 1000) //歌词行处理函数 } }) handleLyric (currentTime) { //控制歌词滚屏 随播放进度不断触发 let lines = [{time: 0, txt: ''}], lyric = this.currentLyric, lineNum lines = lines.concat(lyric.lines) //进行歌词对应时间、内容 //判断当前播放时间位置 进行歌词行的调整 使其当前歌词部分处于中间,若开头则从头部往下,尾部反之 for (let i = 0; i < lines.length; i++) { if (i < lines.length - 1) { let time1 = lines[i].time, time2 = lines[i + 1].time if (currentTime > time1 && currentTime < time2) { lineNum = i - 1 break; } } else { lineNum = lines.length - 2 } } this.currentLineNum = lineNum, this.currentText = lines[lineNum + 1] && lines[lineNum + 1].txt let toLineNum = lineNum - 5 if (lineNum > 5 && toLineNum != this.toLineNum) { this.toLineNum = toLineNum } },
在项目中有不少部分是很是类似的,将这部分进行组件封装经过v-if、class、style等进行添加修改组件部分,组件封装要易于维护,高性能,低耦合。这里展现下首页中部组件
<scroll-view scroll-y="true" enable-back-to-top="true" class="mid-top"> <swiper-t :menu="menu"></swiper-t> <mid-t :midimg="midimg"></mid-t> <foot-t :footimg="footimg" :footname="footname1"></foot-t> <foot-t :footimg="footimg2" :footname="footname2"></foot-t> <foot-t :footimg="footimg3" :footname="footname3"></foot-t> </scroll-view>
虽然只是写了三个页面,但也确实让我体会到了mpvue框架好用的地方,对于那些不太熟悉小程序对vue比较熟悉的人mpvue是一个好用的框架了,但还有不少地方须要完善。文章写到这里其实还有东西可写,好比切歌 播放模式等等,这里就不一一阐述了。写文章,志在分享,志在认识更多志同道合的盆友,也算是本身的一个总结梳理吧。这里附上个人项目地址,有兴趣的朋友能够看看玩玩,帮忙点个star~做为一个即将出去闯荡的大三学生,时间是真的很宝贵,对待学习也不敢懈怠,若是你有什么好的建议或者问题欢迎加我qq:1404827307,写文章不易,且赞且珍惜。前端路漫漫,稳步向前行!