Vue练手项目:手机音乐播放器

本项目是本人2019年学习vue的练手项目,此文记录项目练习过程中的的一些细节和难点。(文章最后更新时间:2019/12/15)

本文目录

  • 1.项目使用技术
  • 2.架构搭建:vue-cli3.x版本
  • 3.项目资源准备
  • 4.后台服务启动
  • 5.解决跨域问题
  • 6.移动端适配
  • 7.轮播图插件
  • 8.使用Axios获取轮播图数据
  • 9.添加图片懒加载
  • 10.封装mheader组件
  • 11.背景图片模糊
  • 12.组件绑定数据的优化处理
  • 13.better-scroll优化滚动
  • 14.格式化歌曲数据
  • 15.使用mixin快速开发
  • 16.请求接口优化
  • 17.添加转场动画
  • 18.全屏播放器动画
  • 19.配置vuex
  • 20.歌曲的播放与模式转换
  • 21.进度条
  • 22.歌词的滚动效果
  • 23.删除歌曲

1.项目使用技术

工具类:vue-cli、webpack、eslint、eruda(手机上的真机调试工具)
框架:vue.js
插件:vue-router、vuex、axios、better-scroll

2.架构搭建:vue-cli3.x版本

安装新版本
npm install -g @vue/cli
安装之后vue -V可以查看当前版本
命令行工具中输入vue ui 即可通过浏览器来管理我们的项目
创建项目
进入到想要创建项目的目录
vue create musicapp
接下来选择配置,选择安装更多配置之vuex,router,css

Use history mode for router?
不使用hsitory模式,因为history模式服务器需要做更多的配置才行

选择less预处理器

选择使用ESlint+Airbnb config

为了项目更加的直观,选择将所有的配置存放到专门的config文件中去
等等

所有配置选择之后,项目就开始自动下载和安装中。
安装之后npm run serve项目就可以启动运行了。

项目目录分析:
3.x版本的public文件夹对应的2.x版本的static文件夹,存放的是静态资源
src文件夹是我们主要写代码的地方
另外还有一些配置文件,因为我们在选择配置的时候选择的是把这些配置都单独划分出来,否则的话这些配置都会塞进package.json中

之前的vue-cli生产的项目文件,webpack配置信息都写在专门的build文件夹和config中,但是3.x版本中,却没有这些文件夹,如何把webpack的配置给输出出来呢?
vue inspect > out.js可以将项目的webpack配置输出出来
输出出来后我们可以很清晰的看到所有的webpack配置了,那么接下来如果我们想要添加一些自己的配置,该怎么做呢?
我们可以在项目根目录下新建一个vue.config.js文件,用来写一些我们的配置,代理服务器的配置代码就是写在这里面的。

3.项目资源准备

我们在开始一个项目的时候,通过会使用reset.css来进行样式的重置,一般还会引用一个common.css来添加一些公共样式,另外还可能会使用到字体图标iconfont.css,这些css肯定是要全局使用的,在vue中,我们只需要把这些文件在main.js进行引用就可以全局生效了

import 'swiper/dist/css/swiper.css'
import './assets/css/reset.css'
import './assets/css/common.css'
import './assets/fonts/iconfont.css'

4.后台服务启动

在github有热心同学用nodejs写了一个网易云音乐API,我们可以进行安装和使用。
步骤:git clone xxx 项目根目录中npm install
然后node app.js,项目就可以启动运行了。

5.解决跨域问题

因为项目是在webpack中搭建的,所以我们可以利用webpack提供的插件来解决。
在vue.config.js添加以下代码:

    devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: false,
                pathRewrite: {
                    '/api': '/'
                }
            }
        }
    }

/api是为了让项目的代理服务去识别需要进行代理的标记
最终发起请求时,再去把/api的api去掉

6.移动端适配

在pc端只有一个视口,那就是视觉视口。
但是在移动端,跨域分为三个视口(viewport)

  • 视觉视口:代表浏览器可视区域的大小
  • 布局视口:网页布局的区域
  • 理想视口:不需要用户缩放和横向滚动条就能正常的查看网站的所有内容。
    通常在移动端我们会在页面代码中加入一个meta标签
    <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
    本项目我们使用的解决方案是手淘的flexible
    安装
    npm install lib-flexible --save
    在main.js中引入
    import 'lib-flexible'
    接下来把public文件夹中的index.html页面中的meta,name=viewport标签去掉,因为这个标签的作用flexible可以去自动完成。
    flexible会动态的设置html的font-size,这点可以在页面的调试代码中看到。
    安装专门用来自动计算rem的插件
    npm install postcss-pxtorem --save
    在项目目录下新建一个postcss.config.js添加配置代码
module.exports = {
  plugins: {
    autoprefixer: {},
    'postcss-pxtorem':{
        //设计稿对应的rem尺寸,此时是iPhone6对应的75px
      rootValue:75,
        //所有元素的px自动转化成rem
      propList:['*']
    }
  },
};

大写的PX不会被转换成rem,适合边框使用

7.轮播图插件

衡量、选择插件/库的需要考量的几个方面

  • 所提供的功能是否能满足我们的需求
  • GitHub上面的star数量
  • issue的提交量,存在量
  • 大厂背景
  • 文档写的如何(这点很重要)

这里,我们选择使用vue-awesome-swiper
安装:npm install vue-awesome-swiper
因为这个轮播图我们有可能在项目的多个地方都用到,所以我们选择全局引用。
全局引用: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获取轮播图数据

安装:npm install axios --save
然后在我们想要使用的页面中进行引用
import axios from 'axios'
然后再methods中写发请求的逻辑代码

async getNewSongs() {
    //我们通过axios拿取到的数据,会经过axios的封装成一个包含
    //headers、status等属性的对象,原始数据就是data属性,
    //我们这里就是通过解构赋值去直接获取到对象中的data的属性值。
    const { data } = await axios.get("/api/personalized/newsong");
    if (data.code === 200) {
        this.newSongsData = data.result;
    }
},

拿到之后把数据渲染到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>

这时候我们会发现请求到的图片非常的大,这样会增加内存消耗,所以我们可以img标签请求图片资源的时候添加参数
:src="${item.song.album.blurPicUrl}?param=400y400"
这样请求到的图片大小会被压缩

9.添加图片懒加载

当页面比较大的时候,如果在进入的时候就默认把页面的全部图片都加载的话,会造成资源的浪费,我们可以使用懒加载技术来进行优化。
这里,我们选用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中的引用路径不会经过webpack的编译,所以我们在main.js引用图片最好要使用绝对路径。这样的话我们需要提前知道图片在打包之后所在的位置。public文件夹下的东西,webpack在打包编译的时候,会把里面的资源直接拷贝到编译后的文件根目录中。所以我们可以把要在main.js中引用的图片先拷贝到public文件夹中,然后引用的时候直接以“/”开头就行了。
接下来我们在项目中引用到图片的地方,把:src改成v-lazy就可以实现图片懒加载了。
更改加载过渡图片的样式:(这里把加载图片缩小为原来的0.3倍)

img[lazy=loading] {
    transform: scale(0.3);
}

10.封装mheader组件

header在很多地方可以用到,但是在一些地方的背景色不同,所以封装的时候可以动态的传递一个背景色数据
首先需要新建一个组件文件,注意header已经是标签名了,所以不能做再命名为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默认为true,当变量red为true时,把类名red赋予组件。这样的话,在组件使用的时候,如果不传递red值,则默认为有类名red所赋予的背景色,如果red传递值为false,则不会有模拟的背景色。
我们在引用mheader组件的时候,在组件标签内添加的代码可以自动渲染到<slot>中。

11.背景图片模糊

思路:通过伪类的方式,为盒子本来的背景图片上再添加一层图片,进行模糊处理。

div{
  background-image: url("./bg.png");

  &:after{
    content: '';
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: inherit;
    filter:blur(20px);
  }
}

只所以用伪类的方式再添加一层,而不在原本的背景上进行模糊,是因为fiter进行模糊的范围并不完全和原本的背景贴合,所以通过伪类,我们可以很方便的对模糊的位置进行调整。经过优化调整,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);
  }
}

这个时候模糊处理后的伪类背景会超过原本的盒子范围,我们可以在恰当的地方添加上overflow: hidden;

12.组件绑定数据的优化处理

如下情况:

<top :title="newSongsData[0].name" 
:img="newSongsData[0].picurl" 
:count="formatData.length"></top>

浏览器会报一些警示错误,原因是axios发起的请求是异步的,当页面最初渲染的时候数据并没有拿到,所以要传递的三个数据也就不存在。解决方法之一是用v-if

<top v-if="newSongsData.length" 
:title="newSongsData[0].name" 
:img="newSongsData[0].picurl" 
:count="formatData.length"></top>

这样做的缺点是一些情况下就算请求完成了newSongsData也是没有数据的,长度自然始终为0。所以我们可以通过computed计算属性进行优化

<top :title="title" :img="img" 
:count="formatData.length"></top>

computed: {
    title() {
        if (this.formatData.length > 0) {
            return this.formatData[0].name;
        } else {
            return "暂无数据";
        }
    },
    img() {
        if (this.formatData.length > 0) {
            return this.formatData[0].al.picUrl;
        } else {
            return "";
        }
    }
},

这样处理还有一个好处就是当数据真的不存在的时候变量也会有默认值,增加了用户体验。

13.better-scroll优化滚动

better-scroll不依赖任何框架,滚动的最外层盒子.wrapper,作用于其中的第一个元素,但是又不影响盒子内的其它元素。但是实际开发中我们给想要滚动的盒子内只添加一个子元素,以便更好的实现滚动。
.wrapper的高度一定要是固定的。
安装:npm install better-scroll --save
因为这个插件我们会在项目的很多个地方都用到,同时还会有一些方法:比如实例化方法和数据改变之后重新刷新的方法,为了避免这些引用和方法多次的书写,我们可以把这个插件再做进一步的封装。
首先封装一个scroll组件scroll.vue
详细代码“Vue插件”文集中有详细讲解。
在想使用scroll插件的页面中,首先需要引用
import scroll from "@/components/scroll";
html结构

<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.格式化歌曲数据

在多个地方请求获得的歌曲数据,数据格式并不一样,这样的话我们用同一个组件去渲染,会发生数据渲染错误的现象,虽然这在正常的工作开发很少遇到,但是多一点准备总是必要的。
我们可以自己封装一个工具,专门用来格式化指定的数据
首先在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快速开发

组件的作用是可以实现复用,但是组件的使用都是全部使用,但是有些情况,我们会用到这个组件的一部分和另外一个组件的一部分,这时候应该怎么办呢?解决方法就是vue提供的mixins属性
这个需要多次复用的代码我们可以看做是一个工具,在common文件夹下的js文件夹下新建一个infoMixin.js文件,把打算多次复用的代码写入

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 "暂无数据";
            }
        },
        img() {
            if (this.formatData.length > 0) {
                return this.formatData[0].al.picUrl;
            } else {
                return "";
            }
        }
    },
}

然后在使用这些代码的页面中进行引用
import infoMixin from '../../common/js/infoMixin'
添加minxins属性进行挂载使用
mixins:[infoMixin]

16.请求接口优化

当进入首页的时候,会发起关于首页的请求,然后进入到排行榜,会发起关于排行榜的请求,然后进入到歌曲详情,会发起关于歌曲详情的请求,这时候,我们点击后退,当后退到指定页面时,关于该页面的请求又会重新发送一遍,这样会增大服务器的压力,实际操作中用户频繁的前进和后退的时间间隔非常的短,数据并不会有什么变化,其实并不用重新更新页面。

产生这种情况的原因是因为把所有的路由都规划成了平级的关系,然进入到某个页面的时候,router.js会销毁路由,然后重新建立路由,所以每次前进和后退,都是一次路由的销毁与重新建立。

解决方法是将整个项目的路由规划成合理的父子路由关系,这样的话router就会很好的分辨出各个页面的层级关系,从而让进入子路由页面的时候,router并不会完全销毁父路由。

vue还提供了另外一种解决方案,就是keep-alive
比如把App.vue写成下面这样的

<template>
  <div id="app">
        <keep-alive>
            <router-view/>
        <keep-alive>
  </div>
</template>

这样我们在进入项目的所有页面,然后后退,的确页面都不会重新请求和渲染,但是这时候发现在重新进入页面的时候,所有进入过的页面都变成静态的了,甚至连一些需要动态传参的页面也变成首次进入的那张静态页面了,这并不符合开发需求,所以这种方式只适合于固定的页面渲染,而不适合动态的页面渲染。

所以我们选用的是改造路由的方式,也就是改造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>,当路由识别到我们要进入子页面的时候,会把子页面渲染到这个<router-view></router-view>中,同理,所有存在父子关系的页面,都应该在父页面放一个<router-view></router-view>以便路由进行识别和渲染。
这个时候我们发现进入子页面的时候,子页面是渲染在父页面的下方的,这是因为我们放置router-view的时候也是放置在父页面的最下方,解决方法是我们给子页面的div增加一个page样式,用来覆盖在父页面的上方

.page {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #f3f4f9;
    z-index: 9999;
    overflow: scroll;
}

17.添加转场动画

给打算做转场动画的所有router-view都包裹在transition标签中

<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.全屏播放器动画

项目的所有项目下默认都会有一个默认的mini播放器,同时也会有一个专门的全屏播放器也页面,这两个播放器显示和隐藏是相反的,所以我们在新建的player组件中(我们把player组件直接引入到App.vue中),可以在data中定义一个fullScreen变量(默认值为true),然后对两个播放器的div进行绑定

    <div>
        <div v-show="!fullScreen" class="mini-player"></div>
        <div v-show="fullScreen" class="player"></div>
    </div>

两个播放器的z-index都不小于前面所说的page样式,否则会显示不出来,可以也设置成9999

当mini播放器和全屏播放器切换的时候,全屏播放器会有一个透明度从0到1的过渡动画,同时全屏播放器的头部区域player-header有一个从上往下的过渡以及底部player-operate有一个从下往上的过渡。
把全屏播放器player包裹在<transition name="player">标签中,然后添加上样式代码就可以实现效果了

.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

整个项目的很多个地方都可以控制播放器,很显然这需要大量的跨组件通讯,所以需要配置vuex进行状态管理
vue-cli创建目录文件中本来就知道store.js,但是state、mutations等都是集合在一起的,我们需要新建一个store文件夹,去进行进一步的细分和管理。
新建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

最后修改一下项目入口文件main.js的store引入路径就完成了vuex的配置了
import store from './store/index';

20.歌曲的播放与模式转换

使用示例:歌曲的循环模式mode,mode的值我们可以设置的更加语义化,同时把这个值的设置单独写出来,写在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中去动态计算并返回这个值

mode(state) {
    return state.mode
}

我们在要改变这个mode值的页面中先使用语法糖引用mutations
import { mapMutations } from 'vuex'
然后就可以将mutations中的方法映射到methods属性中

methods: {
    ...mapMutations([
        'SET_MODE'
    ]),
    addToPlay(item,index){
        this.SET_MODE(1)
    }
}

在刚开始拿到歌曲信息的时候,主要都是些歌曲的基本信息,歌曲的播放链接和歌词需要根据歌曲信息的id值去发送对应的请求获得。
在拿到歌曲的链接之后我们就可以把链接赋值给页面中的audio标签
<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();
    }
}

上一首下一首功能的实现是通过改变currentSongIndex的值来实现的,同时监听了currentSongIndex的变化,当currentSongIndex变化的时候,会触发请求歌曲信息以及歌曲资源等事件。

切换播放模式,尤其是随机模式,打乱的是备份出来的播放列表,而且注意打乱之后,当前播放歌曲的index也需要进行更新

const newIndex = newPlayList.findIndex(
    item => item.id === this.currentSong.id
);

打乱播放列表的函数:

getRandomList(arr) {
    const newArr = [].concat(arr);
    return newArr.sort((a, b) => (Math.random() > 0.5 ? -1 : 1));
}

三种播放模式的样式判断(在计算属性computed中)

modeIcon() {
    return this.mode === playMode.sequence
        ? "icon-liebiaoxunhuan"
        : this.mode === playMode.loop
        ? "icon-danquxunhuan"
        : "icon-suiji";
}

把其绑定在启动的标签上
<i class="iconfont ft-40" :class="modeIcon"></i>

21.进度条

audio标签自带timeupdate事件,当歌曲播放的时候会去自动触发
@timeupdate="updataTime"

updataTime(e) {
    if (!this.touchbarWillMove) {
        this.currentTime = e.target.currentTime;
        this.overTime = e.target.duration;
    }
    // 歌词滚动
    if (this.lyricData) {
        this.moveLyric();
    }
}

计算已播放的百分比(在计算属性computed中)

barPercent() {
    let per = this.currentTime / this.overTime;
    if (per === 0) {
        return 0;
    }
    //四舍五入
    per = Number(per * 100).toFixed();
    return `${per}%`;
}

这个百分比我们可以动态的赋值给进度条的width和小圆点的left
:style="{width:${barPercent}}"

实现小圆点的拖动功能

<div
    class="bar-btn"
    :style="{left:`${barPercent}`}"
    @touchmove.prevent="progressMove"
    @touchend="progressEnd"
></div>

手指按着小圆点移动的时候触发touchmove=>progressMove
结束的时候触发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;
    // 拖动点相当于进度条所占的宽度
    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.歌词的滚动效果

一开始拿到的初始歌词数据是一整个字符串,每一句歌词以\n分割
所以首先把其转换成数组,然后将每一行歌词的时间和真正的歌词分割出来,存放到真正可以使用的歌词信息数组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) {
                // 计算出来每一行歌词的时间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中,就可以静态展示歌词信息了,接下来实现歌词的动态滚动

moveLyric() {
    this.currentLineNumber = this.findCurrentNumber(
        this.currentTime * 1000
    );
    // 当歌词高亮在超过第六行的时候才进行滚动,这样可以让高亮行在页面中间
    if (this.currentLineNumber > 6) {
        // scrollToElement是写在scroll组件里的方法
        // scrollTo也是写在组件里的,在本页面中引用后就可以直接使用
        this.$refs.lyricScroll.scrollToElement(
            this.$refs.lyricLine[this.currentLineNumber - 6],
            1000
        );
    } else {
        // 当高亮行小于6行的时候
        this.$refs.lyricScroll.scrollTo(0, 0, 1000);
    }
},
findCurrentNumber(time) {
    // 返回第一个被匹配到的歌词的下标
    for (let i = 0; i < this.lyricLines.length; i++) {
        if (time < this.lyricLines[i].time) {
            return i - 1;
        }
    }
    // 当当前播放时间大于所有歌词的时间时,默认返回最后一行歌词的下标
    return this.lyricLines.length - 1;
},

这个动态滚动歌词的方法应该在歌词播放时间变化的时候去触发,并且在拖到进度条改变播放时间的时候触发。

23.删除歌曲

根据拿到的id值去找出播放列表中符合的那首歌曲,然后删除掉

DEL_FROM_PLAY_LIST(state, val) {
    const index = state.playList.findIndex(item => item.id === val.delSong.id)
    state.playList.splice(index, 1)
    // 当删除的歌曲不是当前的歌曲的时候,
    // 根据传过来的当前播放歌曲的curSong.id,去查找更新后的歌曲列表playList
    // 拿到更新后的当前播放歌曲的新的currentIndex
    if (val.delSong.id !== val.curSong.id) {
        state.currentIndex = state.playList.findIndex(item => item.id === val.curSong.id)
    }
},
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容