Vue-music 项目学习笔记:播放器内置组件开发(一)

前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记。

一、播放器Vuex数据设计

  • 需求: 播放器可以通过歌手详情列表、歌单详情列表、排行榜、搜索结果多种组件打开,因此播放器数据一定是全局的

  • state.js目录下:定义数据

    import {playMode} from '@/common/js/config'
    
    const state = {
            singer: {},
            playing: false, //播放状态
            fullScreen: false, //播放器展开方式:全屏或收起
            playlist: [], //播放列表(随机模式下与顺序列表不同)
            sequenceList: [], //顺序播放列表
            mode: playMode.sequence, //播放模式: 顺序、循环、随机
            currentIndex: -1 //当前播放歌曲的index(当前播放歌曲为playlist[index])
    }
    
  • common->js目录下:创建config.js配置项目相关

    //播放器播放模式: 顺序、循环、随机
    export const playMode = {
           sequence: 0, 
           loop: 1,
           random: 2
    }
    
  • getter.js目录下:数据映射(类似于计算属性)

    export const playing = state => state.playing
    export const fullScreen = state => state.fullScreen
    export const playlist = state => state.playlist
    export const sequenceList = state => state.sequenceList
    export const mode = state => state.mode
    export const currentIndex = state => state.currentIndex
    
    export const currentSong = (state) => {
              return state.playlist[state.currentIndex] || {}
    }
    

    组件中可以通过mapgetters拿到这些数据

  • mutaion.js目录下:操作state

    const mutations = {
           [types.SET_SINGER](state, singer){
                 state.singer = singer
           },
           [types.SET_PLAYING_STATE](state, flag){
                 state.playing = flag
           },
           [types.SET_FULL_SCREEN](state, flag){
                 state.fullScreen = flag
           },
           [types.SET_PLAYLIST](state, list){
                 state.playlist = list
           },
           [types.SET_SEQUENCE_LIST](state, list){
                 state.sequenceList = list
           },
           [types.SET_PLAY_MODE](state, mode){
                 state.mode = mode
           },
           [types.SET_CURRENT_INDEX](state, index){
                 state.currentIndex = index
           }
    }
    

二、播放器Vuex的相关应用

  • components->player目录下:创建player.vue

    基础DOM

    <div class="normal-player">
            播放器
    </div>
    <div class="mini-player"></div>
    
  • App.vue中应用player组件:因为它不是任何一个路由相关组件,而是应用相关播放器,切换路由不会影响播放器的播放

    <player></player>
    
  • player.vue中获取数据:控制播放器的显示隐藏

    import {mapGetters} from 'vuex'
    
    computed: {
          ...mapGetters([
               'fullScreen',
               'playlist'
          ])
    }
    

    通过v-show判断播放列表有内容时,显示播放器,依据fullScreen控制显示不同的播放器

  • song-list.vue中添加点击播放事件:基础组件不写业务逻辑,只派发事件并传递相关数据

    @click="selectItem(song, index)
    
    selectItem(item, index){
          this.$emit('select', item, index)
    }
    

    子组件行为,只依赖本身相关,不依赖外部调用组件的需求,传出的数据可以不都使用

  • music-list.vue中监听select事件

    <song-list :songs="songs" @select="selectItem"></song-list>
    
    • 设置数据,提交mutations:需要在一个动作中多次修改mutations,在actions.js中封装

      import * as types from './mutation-types'
      
      export const selectPlay = function ({commit, state}, {list, index}) {
               //commit方法提交mutation
               commit(types.SET_SEQUENCE_LIST, list)
               commit(types.SET_PLAYLIST, list)
               commit(types.SET_CURRENT_INDEX, index)
               commit(types.SET_FULL_SCREEN, true)
               commit(types.SET_PLAYING_STATE, true)
      }
      
    • music-list.vue中代理actions,并在methods中调用:

      import {mapActions} from 'vuex' 
      
      selectItem(item, index){
              this.selectPlay({
                   list: this.songs,
                   index
              })
      }
      ...mapActions([
              'selectPlay'
      ])
      

三、播放器基础样式及歌曲数据的应用

  • 通过mapGetter获取到currentSong数据填入到DOM中:点击切换播放器展开收起,需要修改fullScreen
import {mapGetters, mapMutations} from 'vuex'

methods: {
     back() {
          //错误做法: this.fullScreen = false
          //正确做法: 通过mapMutations写入 
          this.setFullScreen(false)
     },
     open() {
          this.setFullScreen(true)
     },
     ...mapMutations({
          setFullScreen: 'SET_FULL_SCREEN'
     })
}

四、播放器展开收起动画

  • 需求:normal-player背景图片渐隐渐现,展开时头部标题从顶部下落,底部按钮从底部回弹,收起时相反

  • 实现:动画使用,回弹效果使用贝塞尔曲线

    • normal-player设置动画

      &.normal-enter-active, &.normal-leave-active
               transition: all 0.4s
               .top, .bottom
                    transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32)
      &.normal-enter, &.normal-leave-to
               opacity: 0
               .top
                    transform: translate3d(0, -100px, 0)
               .bottom
                    transform: translate3d(0, 100px, 0)
      
    • mini-player设置动画

      &.mini-enter-active, &.mini-leave-active
            transition: all 0.4s
      &.mini-enter, &.mini-leave-to
            opacity: 0
      
  • 需求:展开时,mini-player的专辑图片从原始位置飞入CD图片位置,同时有一个放大缩小效果, 对应顶部和底部的回弹;收起时,normal-player的CD图片从原始位置直接落入mini-player的专辑图片位置

  • 实现:Vue提供了javascript事件钩子,在相关的钩子中定义CSS3动画即可

    • 利用第三方库:create-keyframe-animation 使用js编写CSS3动画

    • github地址:https://github.com/HenrikJoreteg/create-keyframe-animation

    • 安装:

      npm install create-keyframe-animation --save
      
    • 引入:

      import animations from 'create-keyframe-animation'
      
      <transition name="normal" @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave">
      
    • methods中封装函数_getPosAndScale获取初始位置及缩放尺寸: (计算以中心点为准)

      _getPosAndScale(){ 
             const targetWidth = 40 //mini-player icon宽度
             const width = window.innerWidth * 0.8 //cd-wrapper宽度
             const paddingLeft = 40 
             const paddingTop = 80
             const paddingBottom = 30 //mini-player icon中心距底部位置
             const scale = targetWidth / width
             const x = -(window.innerWidth / 2 - paddingLeft) //X轴方向移动的距离
             const y = window.innerHeight - paddingTop - width / 2 - paddingBottom
             return {
                   x,
                   y, 
                   scale
             }
      }
      
    • 定义事件钩子方法:

      //事件钩子:创建CSS3动画
      enter(el, done){
              const {x, y, scale} = this._getPosAndScale()
      
              let animation = {
                     0: {
                        transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
                     },
                     60: {
                        transform: `translate3d(0, 0, 0) scale(1.1)`
                     }, 
                     100: {
                        transform: `translate3d(0, 0, 0) scale(1)`
                     }
              }
      
              animations.registerAnimation({
                     name: 'move',
                     animation,
                     presets: {
                        duration: 400,
                        easing: 'linear'
                     }
              })
      
             animations.runAnimation(this.$refs.cdWrapper, 'move', done)
      },
      afterEnter() {
             animations.unregisterAnimation('move')
             this.$refs.cdWrapper.style.animation = ''
      },
      leave(el, done){
             this.$refs.cdWrapper.style.transition = 'all 0.4s'
             const {x, y, scale} = this._getPosAndScale()
             this.$refs.cdWrapper.style[transform] = `translate3d(${x}px, ${y}px, 0) scale(${scale})`
             this.$refs.cdWrapper.addEventListener('transitionend', done)
      },
      afterLeave(){
             this.$refs.cdWrapper.style.transition = ''
             this.$refs.cdWrapper.style[transform] = ''
      }
      
    • transform属性使用prefix自动添加前缀:

      import {prefixStyle} from '@/common/js/dom'
      const transform = prefixStyle('transform')
      
posted @ 2019-02-25 20:03  zllmh  阅读(132)  评论(0编辑  收藏  举报