前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记。
一、播放器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')
-