Vue練手項(xiàng)目:手機(jī)音樂播放器

本項(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)
    }
},
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末次酌,一起剝皮案震驚了整個濱河市恨课,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌和措,老刑警劉巖庄呈,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異派阱,居然都是意外死亡诬留,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門贫母,熙熙樓的掌柜王于貴愁眉苦臉地迎上來文兑,“玉大人,你說我怎么就攤上這事腺劣÷陶辏” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵橘原,是天一觀的道長籍铁。 經(jīng)常有香客問我,道長趾断,這世上最難降的妖魔是什么拒名? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮芋酌,結(jié)果婚禮上增显,老公的妹妹穿的比我還像新娘。我一直安慰自己脐帝,他們只是感情好同云,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堵腹,像睡著了一般炸站。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疚顷,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天武契,我揣著相機(jī)與錄音,去河邊找鬼荡含。 笑死咒唆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的释液。 我是一名探鬼主播全释,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼误债!你這毒婦竟也來了浸船?” 一聲冷哼從身側(cè)響起妄迁,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎李命,沒想到半個月后登淘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡封字,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年黔州,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阔籽。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡流妻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笆制,到底是詐尸還是另有隱情绅这,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布在辆,位于F島的核電站证薇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匆篓。R本人自食惡果不足惜浑度,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奕删。 院中可真熱鬧,春花似錦疗认、人聲如沸完残。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谨设。三九已至,卻和暖如春缎浇,著一層夾襖步出監(jiān)牢的瞬間扎拣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工素跺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留二蓝,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓指厌,卻偏偏與公主長得像刊愚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子踩验,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容