本項(xiàng)目是本人2019年學(xué)習(xí)vue的練手項(xiàng)目写妥,此文記錄項(xiàng)目練習(xí)過程中的的一些細(xì)節(jié)和難點(diǎn)居凶。(文章最后更新時間:2019/12/15)
本文目錄
- 1.項(xiàng)目使用技術(shù)
- 2.架構(gòu)搭建:vue-cli3.x版本
- 3.項(xiàng)目資源準(zhǔn)備
- 4.后臺服務(wù)啟動
- 5.解決跨域問題
- 6.移動端適配
- 7.輪播圖插件
- 8.使用Axios獲取輪播圖數(shù)據(jù)
- 9.添加圖片懶加載
- 10.封裝mheader組件
- 11.背景圖片模糊
- 12.組件綁定數(shù)據(jù)的優(yōu)化處理
- 13.better-scroll優(yōu)化滾動
- 14.格式化歌曲數(shù)據(jù)
- 15.使用mixin快速開發(fā)
- 16.請求接口優(yōu)化
- 17.添加轉(zhuǎn)場動畫
- 18.全屏播放器動畫
- 19.配置vuex
- 20.歌曲的播放與模式轉(zhuǎn)換
- 21.進(jìn)度條
- 22.歌詞的滾動效果
- 23.刪除歌曲
1.項(xiàng)目使用技術(shù)
工具類:vue-cli偏化、webpack、eslint、eruda(手機(jī)上的真機(jī)調(diào)試工具)
框架:vue.js
插件:vue-router路媚、vuex周瞎、axios苗缩、better-scroll
2.架構(gòu)搭建:vue-cli3.x版本
安裝新版本
npm install -g @vue/cli
安裝之后vue -V可以查看當(dāng)前版本
命令行工具中輸入vue ui 即可通過瀏覽器來管理我們的項(xiàng)目
創(chuàng)建項(xiàng)目
進(jìn)入到想要創(chuàng)建項(xiàng)目的目錄
vue create musicapp
接下來選擇配置,選擇安裝更多配置之vuex声诸,router酱讶,css
Use history mode for router?
不使用hsitory模式彼乌,因?yàn)閔istory模式服務(wù)器需要做更多的配置才行
選擇less預(yù)處理器
選擇使用ESlint+Airbnb config
為了項(xiàng)目更加的直觀泻肯,選擇將所有的配置存放到專門的config文件中去
等等
所有配置選擇之后渊迁,項(xiàng)目就開始自動下載和安裝中。
安裝之后npm run serve項(xiàng)目就可以啟動運(yùn)行了灶挟。
項(xiàng)目目錄分析:
3.x版本的public文件夾對應(yīng)的2.x版本的static文件夾琉朽,存放的是靜態(tài)資源
src文件夾是我們主要寫代碼的地方
另外還有一些配置文件,因?yàn)槲覀冊谶x擇配置的時候選擇的是把這些配置都單獨(dú)劃分出來稚铣,否則的話這些配置都會塞進(jìn)package.json中
之前的vue-cli生產(chǎn)的項(xiàng)目文件箱叁,webpack配置信息都寫在專門的build文件夾和config中,但是3.x版本中榛泛,卻沒有這些文件夾蝌蹂,如何把webpack的配置給輸出出來呢?
vue inspect > out.js可以將項(xiàng)目的webpack配置輸出出來
輸出出來后我們可以很清晰的看到所有的webpack配置了曹锨,那么接下來如果我們想要添加一些自己的配置孤个,該怎么做呢?
我們可以在項(xiàng)目根目錄下新建一個vue.config.js文件沛简,用來寫一些我們的配置齐鲤,代理服務(wù)器的配置代碼就是寫在這里面的。
3.項(xiàng)目資源準(zhǔn)備
我們在開始一個項(xiàng)目的時候椒楣,通過會使用reset.css來進(jìn)行樣式的重置给郊,一般還會引用一個common.css來添加一些公共樣式,另外還可能會使用到字體圖標(biāo)iconfont.css捧灰,這些css肯定是要全局使用的淆九,在vue中,我們只需要把這些文件在main.js進(jìn)行引用就可以全局生效了
import 'swiper/dist/css/swiper.css'
import './assets/css/reset.css'
import './assets/css/common.css'
import './assets/fonts/iconfont.css'
4.后臺服務(wù)啟動
在github有熱心同學(xué)用nodejs寫了一個網(wǎng)易云音樂API毛俏,我們可以進(jìn)行安裝和使用炭庙。
步驟:git clone xxx 項(xiàng)目根目錄中npm install
然后node app.js,項(xiàng)目就可以啟動運(yùn)行了煌寇。
5.解決跨域問題
因?yàn)轫?xiàng)目是在webpack中搭建的焕蹄,所以我們可以利用webpack提供的插件來解決。
在vue.config.js添加以下代碼:
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: false,
pathRewrite: {
'/api': '/'
}
}
}
}
/api是為了讓項(xiàng)目的代理服務(wù)去識別需要進(jìn)行代理的標(biāo)記
最終發(fā)起請求時阀溶,再去把/api的api去掉
6.移動端適配
在pc端只有一個視口腻脏,那就是視覺視口。
但是在移動端银锻,跨域分為三個視口(viewport)
- 視覺視口:代表瀏覽器可視區(qū)域的大小
- 布局視口:網(wǎng)頁布局的區(qū)域
- 理想視口:不需要用戶縮放和橫向滾動條就能正常的查看網(wǎng)站的所有內(nèi)容永品。
通常在移動端我們會在頁面代碼中加入一個meta標(biāo)簽
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
本項(xiàng)目我們使用的解決方案是手淘的flexible
安裝
npm install lib-flexible --save
在main.js中引入
import 'lib-flexible'
接下來把public文件夾中的index.html頁面中的meta,name=viewport標(biāo)簽去掉击纬,因?yàn)檫@個標(biāo)簽的作用flexible可以去自動完成鼎姐。
flexible會動態(tài)的設(shè)置html的font-size,這點(diǎn)可以在頁面的調(diào)試代碼中看到。
安裝專門用來自動計(jì)算rem的插件
npm install postcss-pxtorem --save
在項(xiàng)目目錄下新建一個postcss.config.js添加配置代碼
module.exports = {
plugins: {
autoprefixer: {},
'postcss-pxtorem':{
//設(shè)計(jì)稿對應(yīng)的rem尺寸症见,此時是iPhone6對應(yīng)的75px
rootValue:75,
//所有元素的px自動轉(zhuǎn)化成rem
propList:['*']
}
},
};
大寫的PX不會被轉(zhuǎn)換成rem,適合邊框使用
7.輪播圖插件
衡量殃饿、選擇插件/庫的需要考量的幾個方面
- 所提供的功能是否能滿足我們的需求
- GitHub上面的star數(shù)量
- issue的提交量谋作,存在量
- 大廠背景
- 文檔寫的如何(這點(diǎn)很重要)
這里,我們選擇使用vue-awesome-swiper
安裝:npm install vue-awesome-swiper
因?yàn)檫@個輪播圖我們有可能在項(xiàng)目的多個地方都用到乎芳,所以我們選擇全局引用遵蚜。
全局引用:import vueAwesomeSwiper from 'vue-awesome-swiper'
掛載:Vue.use(vueAwesomeSwiper)
同時,我們還需要全局引用一下swiper的css文件
import 'swiper/dist/css/swiper.css'
vue-awesome-swiper就是對swiper做的封裝奈惑,所以使用方式和demo代碼都可以去swiper的文檔中找
8.使用Axios獲取輪播圖數(shù)據(jù)
安裝:npm install axios --save
然后在我們想要使用的頁面中進(jìn)行引用
import axios from 'axios'
然后再methods中寫發(fā)請求的邏輯代碼
async getNewSongs() {
//我們通過axios拿取到的數(shù)據(jù)吭净,會經(jīng)過axios的封裝成一個包含
//headers、status等屬性的對象肴甸,原始數(shù)據(jù)就是data屬性寂殉,
//我們這里就是通過解構(gòu)賦值去直接獲取到對象中的data的屬性值。
const { data } = await axios.get("/api/personalized/newsong");
if (data.code === 200) {
this.newSongsData = data.result;
}
},
拿到之后把數(shù)據(jù)渲染到html中
<swiper :options="swiperOption">
<swiper-slide v-for="(item,index) in newSongsData" :key="index">
<img :src="`${item.song.album.blurPicUrl}?param=400y400`" alt />
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
這時候我們會發(fā)現(xiàn)請求到的圖片非常的大原在,這樣會增加內(nèi)存消耗友扰,所以我們可以img標(biāo)簽請求圖片資源的時候添加參數(shù)
:src="
${item.song.album.blurPicUrl}?param=400y400"
這樣請求到的圖片大小會被壓縮
9.添加圖片懶加載
當(dāng)頁面比較大的時候,如果在進(jìn)入的時候就默認(rèn)把頁面的全部圖片都加載的話庶柿,會造成資源的浪費(fèi)村怪,我們可以使用懶加載技術(shù)來進(jìn)行優(yōu)化。
這里浮庐,我們選用vue-lazyload插件
安裝:npm install vue-lazyload --save
全局引用:import vuelazyload from 'vue-lazyload'
掛載:
Vue.use(vuelazyload,{
//加載過渡圖片
loading:'/load.gif',
//
error:'/user-bg.png'
})
這里需要注意一下甚负,在views和components代碼中引用圖片,可以使用相對路徑审残,url-loader等插件會自動幫我們把路徑改成打包之后的路徑梭域。但是在main.js中的引用路徑不會經(jīng)過webpack的編譯,所以我們在main.js引用圖片最好要使用絕對路徑维苔。這樣的話我們需要提前知道圖片在打包之后所在的位置碰辅。public文件夾下的東西,webpack在打包編譯的時候介时,會把里面的資源直接拷貝到編譯后的文件根目錄中没宾。所以我們可以把要在main.js中引用的圖片先拷貝到public文件夾中,然后引用的時候直接以“/”開頭就行了沸柔。
接下來我們在項(xiàng)目中引用到圖片的地方循衰,把:src改成v-lazy就可以實(shí)現(xiàn)圖片懶加載了。
更改加載過渡圖片的樣式:(這里把加載圖片縮小為原來的0.3倍)
img[lazy=loading] {
transform: scale(0.3);
}
10.封裝mheader組件
header在很多地方可以用到褐澎,但是在一些地方的背景色不同会钝,所以封裝的時候可以動態(tài)的傳遞一個背景色數(shù)據(jù)
首先需要新建一個組件文件,注意header已經(jīng)是標(biāo)簽名了,所以不能做再命名為header迁酸,這里命名為mheader
<template>
<div class="mheader" :class="{'red':red}">
<i class="iconfont icon-zuo" @click="goBack"></i>
<slot></slot>
</div>
</template>
<script>
export default {
name: "mheader",
data() {
return {};
},
props: {
red: {
type: Boolean,
default: true
}
},
methods: {
goBack() {
this.$router.go(-1);
}
}
};
變量red默認(rèn)為true先鱼,當(dāng)變量red為true時,把類名red賦予組件奸鬓。這樣的話焙畔,在組件使用的時候,如果不傳遞red值串远,則默認(rèn)為有類名red所賦予的背景色宏多,如果red傳遞值為false,則不會有模擬的背景色澡罚。
我們在引用mheader組件的時候伸但,在組件標(biāo)簽內(nèi)添加的代碼可以自動渲染到<slot>中。
11.背景圖片模糊
思路:通過偽類的方式留搔,為盒子本來的背景圖片上再添加一層圖片更胖,進(jìn)行模糊處理。
div{
background-image: url("./bg.png");
&:after{
content: '';
width: 100%;
height: 100%;
left: 0;
top: 0;
background: inherit;
filter:blur(20px);
}
}
只所以用偽類的方式再添加一層催式,而不在原本的背景上進(jìn)行模糊函喉,是因?yàn)閒iter進(jìn)行模糊的范圍并不完全和原本的背景貼合,所以通過偽類荣月,我們可以很方便的對模糊的位置進(jìn)行調(diào)整管呵。經(jīng)過優(yōu)化調(diào)整,less代碼如下:
div{
background-image: url("./bg.png");
&:after{
content: '';
width: calc(100% + 80px);
height: calc(100% + 80px);
left: -40px;
top: -40px;
background: inherit;
filter:blur(20px);
}
}
這個時候模糊處理后的偽類背景會超過原本的盒子范圍哺窄,我們可以在恰當(dāng)?shù)牡胤教砑由蟧verflow: hidden;
12.組件綁定數(shù)據(jù)的優(yōu)化處理
如下情況:
<top :title="newSongsData[0].name"
:img="newSongsData[0].picurl"
:count="formatData.length"></top>
瀏覽器會報一些警示錯誤捐下,原因是axios發(fā)起的請求是異步的,當(dāng)頁面最初渲染的時候數(shù)據(jù)并沒有拿到萌业,所以要傳遞的三個數(shù)據(jù)也就不存在坷襟。解決方法之一是用v-if
<top v-if="newSongsData.length"
:title="newSongsData[0].name"
:img="newSongsData[0].picurl"
:count="formatData.length"></top>
這樣做的缺點(diǎn)是一些情況下就算請求完成了newSongsData也是沒有數(shù)據(jù)的,長度自然始終為0生年。所以我們可以通過computed計(jì)算屬性進(jìn)行優(yōu)化
<top :title="title" :img="img"
:count="formatData.length"></top>
computed: {
title() {
if (this.formatData.length > 0) {
return this.formatData[0].name;
} else {
return "暫無數(shù)據(jù)";
}
},
img() {
if (this.formatData.length > 0) {
return this.formatData[0].al.picUrl;
} else {
return "";
}
}
},
這樣處理還有一個好處就是當(dāng)數(shù)據(jù)真的不存在的時候變量也會有默認(rèn)值婴程,增加了用戶體驗(yàn)。
13.better-scroll優(yōu)化滾動
better-scroll不依賴任何框架抱婉,滾動的最外層盒子.wrapper档叔,作用于其中的第一個元素,但是又不影響盒子內(nèi)的其它元素蒸绩。但是實(shí)際開發(fā)中我們給想要滾動的盒子內(nèi)只添加一個子元素衙四,以便更好的實(shí)現(xiàn)滾動。
.wrapper的高度一定要是固定的患亿。
安裝:npm install better-scroll --save
因?yàn)檫@個插件我們會在項(xiàng)目的很多個地方都用到传蹈,同時還會有一些方法:比如實(shí)例化方法和數(shù)據(jù)改變之后重新刷新的方法,為了避免這些引用和方法多次的書寫,我們可以把這個插件再做進(jìn)一步的封裝惦界。
首先封裝一個scroll組件scroll.vue
詳細(xì)代碼“Vue插件”文集中有詳細(xì)講解挑格。
在想使用scroll插件的頁面中,首先需要引用
import scroll from "@/components/scroll";
html結(jié)構(gòu)
<scroll class="page-info-list" :data="formatData">
<songlist :data="formatData" @clickItem="addToPlay"></songlist>
</scroll>
scroll的page-info-list是賦值高度的樣式沾歪。
.page-info-list {
height: calc(100vh - 380px);
overflow: hidden;
}
這樣的恕齐,scroll就可以正常起作用了。
14.格式化歌曲數(shù)據(jù)
在多個地方請求獲得的歌曲數(shù)據(jù)瞬逊,數(shù)據(jù)格式并不一樣,這樣的話我們用同一個組件去渲染仪或,會發(fā)生數(shù)據(jù)渲染錯誤的現(xiàn)象确镊,雖然這在正常的工作開發(fā)很少遇到,但是多一點(diǎn)準(zhǔn)備總是必要的范删。
我們可以自己封裝一個工具蕾域,專門用來格式化指定的數(shù)據(jù)
首先在src文件夾中新建一個common文件夾,然后新建一個js文件夾到旦,js文件夾中新建一個util.js文件
export function formatSongDetail(val){
const newVal = []
val.forEach((item) =>{
const detail = {}
detail.id = item.id
detail.al = Object.assign({}, item.al || item.album || item.song.album)
detail.ar = [].concat(item.ar || item.artists || item.song.artists)
detail.name = item.name
newVal.push(detail)
})
return newVal
}
在需要使用到這個工具的頁面中直接引入這個工具
import { formatSongDetail } from "../../common/js/util";
然后就可以直接使用formatSongDetail 方法
15.使用mixin快速開發(fā)
組件的作用是可以實(shí)現(xiàn)復(fù)用旨巷,但是組件的使用都是全部使用,但是有些情況添忘,我們會用到這個組件的一部分和另外一個組件的一部分采呐,這時候應(yīng)該怎么辦呢?解決方法就是vue提供的mixins屬性
這個需要多次復(fù)用的代碼我們可以看做是一個工具搁骑,在common文件夾下的js文件夾下新建一個infoMixin.js文件斧吐,把打算多次復(fù)用的代碼寫入
import top from "@/components/top.vue";
import songlist from "@/components/songlist.vue";
import scroll from "@/components/scroll.vue";
export default{
components: {
top,
songlist,
scroll
},
data() {
return {
formatData:[]
};
},
computed: {
title() {
if (this.formatData.length > 0) {
return this.formatData[0].name;
} else {
return "暫無數(shù)據(jù)";
}
},
img() {
if (this.formatData.length > 0) {
return this.formatData[0].al.picUrl;
} else {
return "";
}
}
},
}
然后在使用這些代碼的頁面中進(jìn)行引用
import infoMixin from '../../common/js/infoMixin'
添加minxins屬性進(jìn)行掛載使用
mixins:[infoMixin]
16.請求接口優(yōu)化
當(dāng)進(jìn)入首頁的時候,會發(fā)起關(guān)于首頁的請求仲器,然后進(jìn)入到排行榜煤率,會發(fā)起關(guān)于排行榜的請求,然后進(jìn)入到歌曲詳情乏冀,會發(fā)起關(guān)于歌曲詳情的請求蝶糯,這時候,我們點(diǎn)擊后退辆沦,當(dāng)后退到指定頁面時昼捍,關(guān)于該頁面的請求又會重新發(fā)送一遍,這樣會增大服務(wù)器的壓力众辨,實(shí)際操作中用戶頻繁的前進(jìn)和后退的時間間隔非常的短端三,數(shù)據(jù)并不會有什么變化,其實(shí)并不用重新更新頁面鹃彻。
產(chǎn)生這種情況的原因是因?yàn)榘阉械穆酚啥家?guī)劃成了平級的關(guān)系郊闯,然進(jìn)入到某個頁面的時候,router.js會銷毀路由,然后重新建立路由团赁,所以每次前進(jìn)和后退育拨,都是一次路由的銷毀與重新建立。
解決方法是將整個項(xiàng)目的路由規(guī)劃成合理的父子路由關(guān)系欢摄,這樣的話router就會很好的分辨出各個頁面的層級關(guān)系熬丧,從而讓進(jìn)入子路由頁面的時候,router并不會完全銷毀父路由怀挠。
vue還提供了另外一種解決方案析蝴,就是keep-alive
比如把App.vue寫成下面這樣的
<template>
<div id="app">
<keep-alive>
<router-view/>
<keep-alive>
</div>
</template>
這樣我們在進(jìn)入項(xiàng)目的所有頁面,然后后退绿淋,的確頁面都不會重新請求和渲染闷畸,但是這時候發(fā)現(xiàn)在重新進(jìn)入頁面的時候,所有進(jìn)入過的頁面都變成靜態(tài)的了吞滞,甚至連一些需要動態(tài)傳參的頁面也變成首次進(jìn)入的那張靜態(tài)頁面了佑菩,這并不符合開發(fā)需求,所以這種方式只適合于固定的頁面渲染裁赠,而不適合動態(tài)的頁面渲染殿漠。
所以我們選用的是改造路由的方式,也就是改造router.js文件
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
children: [
{
path: '/recommend',
name: 'recommend',
component: recommend,
},
{
path: '/rank',
name: 'rank',
component: rank,
children: [
{
path: ':id',
name: 'rankinfo',
component: rankinfo,
}
]
},
]
}
]
});
改造完之后所有的頁面都屬于‘/’的子路由了佩捞,所以我們在home頁面還需要改造一下绞幌,也就是在本來home的頁面元素之下放一個<router-view></router-view>
,當(dāng)路由識別到我們要進(jìn)入子頁面的時候一忱,會把子頁面渲染到這個<router-view></router-view>
中啊奄,同理,所有存在父子關(guān)系的頁面掀潮,都應(yīng)該在父頁面放一個<router-view></router-view>
以便路由進(jìn)行識別和渲染菇夸。
這個時候我們發(fā)現(xiàn)進(jìn)入子頁面的時候,子頁面是渲染在父頁面的下方的仪吧,這是因?yàn)槲覀兎胖胷outer-view的時候也是放置在父頁面的最下方庄新,解決方法是我們給子頁面的div增加一個page樣式,用來覆蓋在父頁面的上方
.page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #f3f4f9;
z-index: 9999;
overflow: scroll;
}
17.添加轉(zhuǎn)場動畫
給打算做轉(zhuǎn)場動畫的所有router-view都包裹在transition標(biāo)簽中
<transition name="slide">
<router-view></router-view>
</transition>
然后添加公共樣式
.slide-enter-active,.slide-leave-active{
transition: all .3s;
}
.slide-enter,.slide-leave-to{
transform: translate3d(100%,0,0)
}
18.全屏播放器動畫
項(xiàng)目的所有項(xiàng)目下默認(rèn)都會有一個默認(rèn)的mini播放器薯鼠,同時也會有一個專門的全屏播放器也頁面择诈,這兩個播放器顯示和隱藏是相反的,所以我們在新建的player組件中(我們把player組件直接引入到App.vue中)出皇,可以在data中定義一個fullScreen變量(默認(rèn)值為true)羞芍,然后對兩個播放器的div進(jìn)行綁定
<div>
<div v-show="!fullScreen" class="mini-player"></div>
<div v-show="fullScreen" class="player"></div>
</div>
兩個播放器的z-index都不小于前面所說的page樣式,否則會顯示不出來郊艘,可以也設(shè)置成9999
當(dāng)mini播放器和全屏播放器切換的時候荷科,全屏播放器會有一個透明度從0到1的過渡動畫唯咬,同時全屏播放器的頭部區(qū)域player-header有一個從上往下的過渡以及底部player-operate有一個從下往上的過渡。
把全屏播放器player包裹在<transition name="player">標(biāo)簽中畏浆,然后添加上樣式代碼就可以實(shí)現(xiàn)效果了
.player-enter-active,
.player-leave-active {
transition: all 0.3s;
opacity: 1;
.player-header,
.player-operate {
transform: translate3d(0, 0, 0);
transition: all 0.3s cubic-bezier(0.86, 0.18, 0.82, 1.32);
}
}
.player-enter,
.player-leave-to {
opacity: 0;
.player-header {
transform: translate3d(0, -100px, 0);
}
.player-operate {
transform: translate3d(0, 100px, 0);
}
}
19.配置vuex
整個項(xiàng)目的很多個地方都可以控制播放器胆胰,很顯然這需要大量的跨組件通訊,所以需要配置vuex進(jìn)行狀態(tài)管理
vue-cli創(chuàng)建目錄文件中本來就知道store.js刻获,但是state蜀涨、mutations等都是集合在一起的,我們需要新建一個store文件夾蝎毡,去進(jìn)行進(jìn)一步的細(xì)分和管理厚柳。
新建actions.js
const actions = {}
export default actions
getters.js
const getters= {}
export default getters
mutations.js
const mutations= {}
export default mutations
state.js
const state= {}
export default state
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import getters from './getters'
import actions from './actions'
Vue.use(Vuex)
const store = new Vuex.Store({
state,
mutations,
getters,
actions
})
export default store
最后修改一下項(xiàng)目入口文件main.js的store引入路徑就完成了vuex的配置了
import store from './store/index';
20.歌曲的播放與模式轉(zhuǎn)換
使用示例:歌曲的循環(huán)模式mode,mode的值我們可以設(shè)置的更加語義化沐兵,同時把這個值的設(shè)置單獨(dú)寫出來草娜,寫在common文件夾的js文件夾中的aliasConfig.js中
export const playMode = {
sequence:0,
loop:1,
random:2
}
在state中引用playMode并且定義mode
import { playMode } from '../../common/js/aliasConfig'
const state = {
mode:playMode.sequence,
}
export default state
在mutations.js中定義改變mode的方法
SET_MODE(state, val) {
state.mode = val
}
在getters.js中去動態(tài)計(jì)算并返回這個值
mode(state) {
return state.mode
}
我們在要改變這個mode值的頁面中先使用語法糖引用mutations
import { mapMutations } from 'vuex'
然后就可以將mutations中的方法映射到methods屬性中
methods: {
...mapMutations([
'SET_MODE'
]),
addToPlay(item,index){
this.SET_MODE(1)
}
}
在剛開始拿到歌曲信息的時候,主要都是些歌曲的基本信息痒筒,歌曲的播放鏈接和歌詞需要根據(jù)歌曲信息的id值去發(fā)送對應(yīng)的請求獲得。
在拿到歌曲的鏈接之后我們就可以把鏈接賦值給頁面中的audio標(biāo)簽
<audio :src="musicData.url" ref="audio" @timeupdate="updataTime" @ended="end"></audio>
通過togglePlay事件去控制音樂的播放與暫停
togglePlay(val) {
if (!this.currentSong) return;
if (val === true || val === false) {
this.playing = val;
} else {
this.playing = !this.playing;
}
const audio = this.$refs.audio;
if (this.playing) {
audio.play();
} else {
audio.pause();
}
}
上一首下一首功能的實(shí)現(xiàn)是通過改變currentSongIndex的值來實(shí)現(xiàn)的茬贵,同時監(jiān)聽了currentSongIndex的變化簿透,當(dāng)currentSongIndex變化的時候,會觸發(fā)請求歌曲信息以及歌曲資源等事件解藻。
切換播放模式老充,尤其是隨機(jī)模式,打亂的是備份出來的播放列表螟左,而且注意打亂之后啡浊,當(dāng)前播放歌曲的index也需要進(jìn)行更新
const newIndex = newPlayList.findIndex(
item => item.id === this.currentSong.id
);
打亂播放列表的函數(shù):
getRandomList(arr) {
const newArr = [].concat(arr);
return newArr.sort((a, b) => (Math.random() > 0.5 ? -1 : 1));
}
三種播放模式的樣式判斷(在計(jì)算屬性computed中)
modeIcon() {
return this.mode === playMode.sequence
? "icon-liebiaoxunhuan"
: this.mode === playMode.loop
? "icon-danquxunhuan"
: "icon-suiji";
}
把其綁定在啟動的標(biāo)簽上
<i class="iconfont ft-40" :class="modeIcon"></i>
21.進(jìn)度條
audio標(biāo)簽自帶timeupdate事件,當(dāng)歌曲播放的時候會去自動觸發(fā)
@timeupdate="updataTime"
updataTime(e) {
if (!this.touchbarWillMove) {
this.currentTime = e.target.currentTime;
this.overTime = e.target.duration;
}
// 歌詞滾動
if (this.lyricData) {
this.moveLyric();
}
}
計(jì)算已播放的百分比(在計(jì)算屬性computed中)
barPercent() {
let per = this.currentTime / this.overTime;
if (per === 0) {
return 0;
}
//四舍五入
per = Number(per * 100).toFixed();
return `${per}%`;
}
這個百分比我們可以動態(tài)的賦值給進(jìn)度條的width和小圓點(diǎn)的left
:style="{width:
${barPercent}}"
實(shí)現(xiàn)小圓點(diǎn)的拖動功能
<div
class="bar-btn"
:style="{left:`${barPercent}`}"
@touchmove.prevent="progressMove"
@touchend="progressEnd"
></div>
手指按著小圓點(diǎn)移動的時候觸發(fā)touchmove=>progressMove
結(jié)束的時候觸發(fā)touchend=>progressEnd
progressMove(e) {
const pageX = e.touches[0].pageX;
this.calcPercent(pageX);
},
calcPercent(x) {
const offsetLeft = this.$refs.progressBar.offsetLeft;
const barWidth = this.$refs.progressBar.clientWidth;
// 拖動點(diǎn)相當(dāng)于進(jìn)度條所占的寬度
let moveWidth = x - offsetLeft;
if (moveWidth > barWidth) moveWidth = barWidth;
if (moveWidth < 0) moveWidth = 0;
let p = moveWidth / barWidth;
this.currentTime = this.overTime * p;
},
progressEnd() {
this.resetPlayer();
},
resetPlayer() {
this.$refs.audio.currentTime = this.currentTime;
this.togglePlay(true);
},
22.歌詞的滾動效果
一開始拿到的初始歌詞數(shù)據(jù)是一整個字符串胶背,每一句歌詞以\n
分割
所以首先把其轉(zhuǎn)換成數(shù)組巷嚣,然后將每一行歌詞的時間和真正的歌詞分割出來,存放到真正可以使用的歌詞信息數(shù)組lyricLines
initLines() {
this.lyricLines = [];
if (this.lyricData) {
const lines = this.lyricData.split("\n");
for (let i = 0; i < lines.length; i++) {
// 拿到某一行的值
const line = lines[i];
// 時間
const timeExp = /\[(\d{2}):(\d{2}\.\d{2,3})\]/g;
// 匹配到每一行的時間
const result = timeExp.exec(line);
if (result) {
// 計(jì)算出來每一行歌詞的時間time
const time =
Number(result[1] * 60 * 1000) +
Number(result[2] * 1000);
const txt = line.replace(timeExp, "").trim();
this.lyricLines.push({
time,
txt
});
}
}
}
},
把lyricLines渲染到歌詞頁面上并放置到scroll中钳吟,就可以靜態(tài)展示歌詞信息了廷粒,接下來實(shí)現(xiàn)歌詞的動態(tài)滾動
moveLyric() {
this.currentLineNumber = this.findCurrentNumber(
this.currentTime * 1000
);
// 當(dāng)歌詞高亮在超過第六行的時候才進(jìn)行滾動,這樣可以讓高亮行在頁面中間
if (this.currentLineNumber > 6) {
// scrollToElement是寫在scroll組件里的方法
// scrollTo也是寫在組件里的红且,在本頁面中引用后就可以直接使用
this.$refs.lyricScroll.scrollToElement(
this.$refs.lyricLine[this.currentLineNumber - 6],
1000
);
} else {
// 當(dāng)高亮行小于6行的時候
this.$refs.lyricScroll.scrollTo(0, 0, 1000);
}
},
findCurrentNumber(time) {
// 返回第一個被匹配到的歌詞的下標(biāo)
for (let i = 0; i < this.lyricLines.length; i++) {
if (time < this.lyricLines[i].time) {
return i - 1;
}
}
// 當(dāng)當(dāng)前播放時間大于所有歌詞的時間時坝茎,默認(rèn)返回最后一行歌詞的下標(biāo)
return this.lyricLines.length - 1;
},
這個動態(tài)滾動歌詞的方法應(yīng)該在歌詞播放時間變化的時候去觸發(fā),并且在拖到進(jìn)度條改變播放時間的時候觸發(fā)暇番。
23.刪除歌曲
根據(jù)拿到的id值去找出播放列表中符合的那首歌曲嗤放,然后刪除掉
DEL_FROM_PLAY_LIST(state, val) {
const index = state.playList.findIndex(item => item.id === val.delSong.id)
state.playList.splice(index, 1)
// 當(dāng)刪除的歌曲不是當(dāng)前的歌曲的時候,
// 根據(jù)傳過來的當(dāng)前播放歌曲的curSong.id壁酬,去查找更新后的歌曲列表playList
// 拿到更新后的當(dāng)前播放歌曲的新的currentIndex
if (val.delSong.id !== val.curSong.id) {
state.currentIndex = state.playList.findIndex(item => item.id === val.curSong.id)
}
},