基于vue的音乐播放器_vue动感音乐包

基于vue的音乐播放器_vue动感音乐包原文链接:https://blog.csdn.net/Forever201295/article/details/80266600一、项目说明该播放器的是基于学习vue的实战练习,不用于其他途径。应

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

原文链接:https://blog.csdn.net/Forever201295/article/details/80266600

 

一、项目说明
该播放器的是基于学习vue的实战练习,不用于其他途径。应用中的全部数据来自于 QQ音乐 移动端(https://m.y.qq.com/),利用 jsonp 以及 axios 代理后端请求抓取。

二、目录结构
目录/文件         说明
api                    与后台数据交互文件
base                 一些与业务逻辑无关的基础组件,例如轮播图:slider组件
common           存放图片,字体,样式,以及js插件等公共资源
components     业务逻辑代码
router                项目路由
store                 vuex状态管理配置
三、base组件
1、轮播图slider
引入组件 better-scroll

1.1、参数设置
loop:是否循环播放
autoPlay:是否自动播放
interval:自动播放的间隔时间
2.1、实现
(1).需要通过获取slider的宽度来设置每一个轮播图和轮播图的包裹层的宽度
(2).初始化better-scroll实例
若设置 loop为true 会自动 clone 两个轮播插在前后位置,如果轮播循环播放,是前后各加一个轮播图保证无缝切换,所以需要再加两个宽度

if (this.loop) {
width += 2 * sliderWidth
}

(3). 给slider绑定’scrollEnd‘事件,来获取当前滚动值currentPageIndex
(4).dots小圆点的active状态。通过currentPageIndex === index 来判断
(5).为了保证改变窗口大小依然正常轮播,监听窗口 resize 事件,通过better-scroll提供的refresh()重新渲染轮播图

window.addEventListener(‘resize’, () => {
if (!this.slider) {
return
}
this._setSliderWidth(true)
this.slider.refresh()
})
}

(6)在组件销毁之前 beforeDestroy 销毁定时器

2 播放进度条组件
2.1 全屏下 条状滚动条progeress-bar
参数设置
percent:显示当前播放进度
实现
a. 拖拽按钮时候:监听touchstart,touchmove,touchend事件
touchstart: 获取第一次点击的横坐标clinetX:startX,整个progress的clientWidth:left。
touchmove:获取移动后的横坐标,计算对应的delta,此时进度条的位置 = clinetWidth+delta
touchend:派发出percent。从而改变progress的width
progressTouchStart(e) {
this.touch.startX = e.touches[0].clientX
this.touch.left = this.$refs.progress.clientWidth
},
progressTouchMove(e) {
let delta = e.touches[0].clientX – this.touch.startX
let offsetWidth = this.touch.left + delta
this._offset(offsetWidth)
},
progressTouchEnd() {
this._triggerPercent()
},

b. 点击时候:也是通过点击的位置计算出progress的宽度。

progressClick(e) {
const rect = this.$refs.progressBar.getBoundingClientRect()
const offsetWidth = e.pageX – rect.left
this._offset(offsetWidth)
this._triggerPercent()
},

注:getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
getBoundingClientRect是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分),
该函数返回一个Object对象,该对象有6个属性:top,lef,right,bottom,width,height;

2.1 mini 圆形滚动条progeress-circle
参数
radius:设置圆形的直径
percent:当前进度
实现
圆形采用svg,中有两个圆,一个是背景圆形,另一个为已播放的圆形进度,圆形进度主要用了stroke-dasharray(描边距离) 和stroke-dashoffset(描边偏移距离) 两个属性的设置来显示对应进度变化
<svg :width=”radius” :height=”radius” viewBox=”0 0 100 100″ version=”1.1″ xmlns=”http://www.w3.org/2000/svg”>
<circle class=”progress-background” r=”50″ cx=”50″ cy=”50″ fill=”transparent”/>
<circle class=”progress-bar” r=”50″ cx=”50″ cy=”50″ fill=”transparent” :stroke-dasharray=”dashArray”
:stroke-dashoffset=”dashOffset”/>
</svg>

viewBox = 0 0 100 100 100 是相对于svg里面的设置
传入radius为32: 100 =》直径 32
r = 50 :50 =》半径 16
stroke-dasharray : 描边距离 (这里对应为 math.PI * 100)
stroke-dashoffset: 描边偏移距离 (设置为 314,则偏移了314,这个圆就没有了。设置为0 ,这个圆完全显示)

computed: {
dashOffset() {
return (1 – this.percent) * this.dashArray //只需根据percent来stroke-dashoffset即可显示进度
}
}

四、api拿后端数据
组件中的数据全都是拿了qq音乐网页版的数据,拿数据的方式有两种,一种可以直接通过jsonp跨域来获取的,另一种接口通过referer伪造请求,
1.jsonp方式
在common中,封装一个公用jsonp方法

import originJsonp from ‘jsonp’

export default function jsonp(url, data, option) {
url += (url.indexOf(‘?’) < 0 ? ‘?’ : ‘&’) + param(data)

return new Promise((resolve, reject) => {
originJsonp(url, option, (err, data) => {
if (!err) {
resolve(data)
} else {
reject(err)
}
})
})
}

2.伪造请求
一些接口在后台简单设置一下 Referer, Host,可以限制前台直接通过浏览器抓到你的接口,但是这种方式防不了后端代理的方式,前端 XHR 会有跨域限制,后端发送 http 请求则没有限制,因此可以伪造请求
vue提供的axios可以在浏览器端发送 XMLHttpRequest 请求,在服务器端发送 http 请求获取;
在webpack.dev.config中配置如下

var express = require(‘express’)
var axios = require(‘axios’)
var app = express()
var apiRoutes = express.Router()
before(apiRoutes){
apiRoutes.get(‘/api/getDiscList’,(req,res)=>{
const url = ‘https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg’;
axios.get(url, {
headers: {
referer: ‘https://c.y.qq.com/’,
host: ‘c.y.qq.com’
},
params: req.query //这是请求的query
}).then((response) => {
//response是url地址返回的,数据在data里。
res.json(response.data)
}).catch((e) => {
console.log(e);
})
});
app.use(‘/api’, apiRoutes);
},
}

定义一个路由,拿到一个 /api/getDiscList 接口,通过 axios 伪造 headers,发送给QQ音乐服务器一个 http 请求,得到服务端正确的响应,通过 res.json(response.data) 返回到浏览器端;
注意:此时的这个接口返回的格式已经是json,应该设置format:json
那么问题来了,大公司怎么防止被恶意代理呢?当你的访问量大的时候,出口ip就会可能被查到获取封禁,还有一种方式就是参数验签,也就是请求人家的数据必须带一个签名参数,然后这个签名参数是很难拿到的这个正确的签名,从而达到保护数据的目的;

五、components业务逻辑代码
1、推荐页面
Scroll 初始化但却没有滚动,是因为初始化时机不对,必须保证数据到来,DOM 成功渲染之后 再去进行初始化
可以使用父组件 给 Scrol组件传 :data 数据,Scroll 组件自己 watch 这个 data,有变化就立刻 refesh 滚动
对应图片可以通过监听onload事件,来进行滚动刷新

<img @load=”loadImage” class=”needsclick” :src=”item.picUrl”>
loadImage() {
if (!this.checkloaded) {
this.checkloaded = true
this.$refs.scroll.refresh()
}
}

新版本 BScroll 已经自己实现检测 DOM 变化,自动刷新,大部分场景下无需传 data 了

2、歌手页面
2.1、数据重构
歌手页面的结构是 热门、 A-Z 的顺序排列,我们这里只抓取100条数据,观察其数据是乱序的,但我们可以利用数据的 Findex 进行数据的重构
1.首先可以定义一个 map 结构

let map = {
hot: {
title: HOT_NAME,
item: []
}
}

接着遍历得到的数据,将前10条添加到热门 hot 里
然后查看每条的 Findex ,如果 map[Findex] 没有,创建 map[Findex] push 进新条目,如果 map[Findex] 有,则向其 push 进新条目

list.forEach((item, index) => {
if (index < HOT_SINGER_LEN) {
map.hot.item.push(new SingerFormat({
id: item.Fsinger_mid,
name: item.Fsinger_name,
}))
}
const key = item.Findex
if (!map[key]) {
map[key] = {
title: key,
items: []
}
}
map[key].items.push(new SingerFormat({
id: item.Fsinger_mid,
name: item.Fsinger_name
}))
})

这样就得到了一个 符合我们基本预期的 map 结构,但是因为 map 是一个对象,数据是乱序的,Chrome 控制台在展示的时候会对 key 做排序,但实际上我们代码并没有做。
所以还要将其进行排序,这里会用到 数组的 sort 方法,所以我们要先把 map对象 转为 数组

let hot = []
let ret = []
let un = []
for (let key in map) {
let val = map[key]
if (val.title.match(/[a-zA-z]/)) {
ret.push(val)
} else if (val.title === HOT_NAME) {
hot.push(val)
} else {
un.push(val)
}
}
ret.sort((a, b) => {
return a.title.charCodeAt(0) – b.title.charCodeAt(0)
})
return hot.concat(ret, un)

这样就拿到一个类似[hot,a,b,c…….]这样符合需求的按规律排的数组。

2.2、锚点操作控制主区块列表
实现效果:点击或滑动 shortcut 不同的锚点 ,自动滚动至相应的标题列表
实现思维:获得每一次操作shortcut上对应的index,想办法通过index来设置左边区块的滚动值
1.如何获得index值
a.点击时候:循环的时候将给dom绑上data-index属性,写上当前index。点击时候通过DOM操作获取(e.target对象的getAttribute)
b.滑动时候:第一次点击触碰 shortcut ,记录触碰位置的 index,y坐标值,在touchmove事件中拿到第二次触碰shortcut的y坐标值y2,将两次触碰的位置的差值处理成索引上的 delta 差值,从而可以拿到第二次触碰的index值。
2.怎么通过index设置滚动值
利用BScroll的scrollToElement可以设置content滚动到某个DOM位置

scrollToElement() {
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
}

通过index值可以知道当前content应该滚动到第几个标题列表里面

_scrollTo(index) {
this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0) //listgroup代表左边标题区块
}

2.3、滑动主列表控制锚点
实现效果:滑动主列表,侧边 shortcut 自动高亮不同锚点
实现思维:实时监听主列表的滑动事件,得到每次滑动的值scrollY,设计一个listHeight数组存放每一个主列表到浏览器顶端的距离,比对scrollY存放在哪个listHeight的区间中得到currentIndex数值,这个currentIndex就对应着shortcut需高亮的锚点
1.scrollY的获取
a.获取滚动值:添加参数“listenScroll”来设置是否实时监听content的滚动事件,并向父组件派发事件scroll传出当前的滚动值:pos.y

if (this.listenScroll) {
let me = this
this.scroll.on(‘scroll’, (pos) => { // 实时监测滚动事件,派发事件:Y轴距离
me.$emit(‘scroll’, pos)
})
}

b.父组件监听到滚动派发的事件,并将值存入scrollY

@scroll=”scroll” //template调用组件时绑定
scroll(pos) {
this.scrollY = pos.y // 实时获取 BScroll 滚动的 Y轴距离
}

2.listHeight的设计实现

_calculateHeight(){
const lists = this.$refs.listGroup //listGroup为主列表区块
let height = 0;
this.listHeight.push(height)
for(let i=0; i<lists.length; i++){
height += lists[i].clientHeight
this.listHeight.push(height)
}
},

3、通过实时watch scrollY的值,比对listHeight,拿到当前content滚动值落在哪个区间,也就拿到了currentIndex

scrollY(newY){ //获取的浏览器滚动的值均为负数
const listHeight = this.listHeight
if(newY > 0){ //当滚动到屏幕顶部时
newY = 0
return
}
for (let i = 0; i < listHeight.length – 1; i++){
let hei_1 = listHeight[i]
let hei_2 = listHeight[i+1]
if(-newY >= hei_1 && -newY < hei_2){
this.currentIndex = i; //currentIndex值是定义锚点高亮的值
this.diff = hei_2 + newY
return
}
}
},

ps:vue用法小记:watch 的 scrollY(newY){}
1.当我们在 Vue 里修改了在 data 里定义的变量,就会触发这个变量的 setter,经过vue的封装处理,会触发 watch 的回调函数,也就是 scrollY(newY) {} 这里的函数会执行,同时,newY 就是我们修改后的值。
2.scrollY 是定义在 data 里的,列表滚动的时候,scroll 事件的回调函数里有修改 this.scrollY,所以能 watch 到它的变化。

2.4 滚动固定标题
实现效果:主列表顶端固定一个标题,显示当前滚动的列表标题,标题改变时有transform上移效果
实现思维:固定标题fixedTitle拿到当前滚动数组中的title数据即可,在主列表的滚动事件中。设计一个diff值:存入每个区块的高度上限(也就是底部)减去 Y轴偏移的值,实时监听diff,当diff小于title块的高度时候,开始上移效果
1.fixedTitle获取

fixedTitle() { //computed中设置
if (this.scrollY > 0) {
return ”
}
return this.data[this.currentIndex] ? this.data[this.currentIndex].title : ”
}

2.diff设置和监听

this.diff = hei_2 + newY //hei_2为即将滚动到下一个listGroup的高度
diff(newVal) {
let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal – TITLE_HEIGHT : 0 ; //TITLE_HEIGHT 为title块的高度:30
if (this.fixedTop === fixedTop) { //设定this.fixedTop的值存入fixedTop值。若滚动过程中fixedTop没有发生变化就不进行transform设置。减少DOM操作,
return
}
this.fixedTop = fixedTop
this.$refs.fixed.style.transform = `translate3d(0,${fixedTop}px,0)`
}

3、歌手详情页
歌手详情页是在歌手页singer跳转至二级路由页 singer-detail
index.js 路由里配置

{
path: ‘/singer’,
component: Singer,
children: [
{
path: ‘:id’, // 表示 id 为变量
component: SingerDetail
}
]
}

在singer页面里面跳转路由设定

selectSinger(singer){
this.$router.push({
path: `/singer/${singer.id}`
})
}

3.1、vuex
由于歌手详情页这个组件在app中有多次调用,这里设计到‘多个组件共享状态’的问题。故采用vuex状态管理,本项目简要介绍如下,具体移步 vuex:
通常的流程为:
– 定义 state,考虑项目需要的原始数据(最好为底层数据)
– getters,就是对原始数据的一层映射,可以只为底层数据做一个访问代理,也可以根据底层数据映射为新的计算数据(相当于 vuex 的计算属性)
– 修改数据:mutations,定义如何修改数据的逻辑(本质是函数),在定义 mutations 之前 要先定义 mutation-types
actions.js 通常是两种操作
– 异步操作
– 是对mutation的封装,比如一个动作需要触发多个mutation的时候,就可以把多个mutation封装到一个action中,达到调用一个action去修改多个mutation的目的。

3.1.1、歌手详情页关于vuex的设置
a.state.js:创建singer对象

const state = {
singer: {},
}
export default state

b.getter.js:对singer对象设置映射

export const singer = state => state.singer

c.mutation-types.js:设置singer可以修改的type值

export const SET_SINGER = ‘SET_SINGER’

D.mutation.js:写入修改singer的函数

import * as types from ‘./mutation-types’

const mutations = {
[types.SET_SINGER](state, singer){
state.singer = singer
}
}

export default mutations

e:index.js:实现vuex配置

import Vue from ‘vue’
import Vuex from ‘vuex’
import * as actions from ‘./actions’
import * as getters from ‘./getters’
import state from ‘./state’
import mutations from ‘./mutations’
import createLogger from ‘vuex/dist/logger’
Vue.use(Vuex)
//非生产模式下开启debug
const debug = process.env.NODE_ENV !== ‘production’
export default new Vuex.Store({
actions,
getters,
state,
mutations,
strict: debug,
plugins: debug ? [createLogger()] : []
})

3.2、利用vuex进行数据传递
实现效果:在singer页面点击进入singer-detai页面。传入歌手信息。显示歌手详情
实现思维:在singer组件跳转路由的时候,将当前点击的歌手信息写入singer的state状态中

首先 listview.vue 检测点击事件,将具体点击的歌手派发出去,以供父组件 singer 监听
selectItem(item) {
this.$emit(‘select’, item) //item即为歌手数据
},

父组件监听事件执行 selectSinger(singer)
selectSinger(singer) {
this.$router.push({
path: `/singer/${singer.id}`
})
this.setSinger(singer)
},

…mapMutations({ /语法糖,’…’将多个对象注入当前对象
setSinger: ‘SET_SINGER’ // 将 this.setSinger() 映射为 this.$store.commit(‘SET_SINGER’)
})

mapMutation为vuex提供的语法糖,获取所有的mutation
3. singer-detail 取 vuex 中存好的数据

computed: {
…mapGetters([ // 将 this.singer映射为 this.$store.getter.singer
‘singer’
])
}

mapGetters为vuex提供的语法糖,获取所有的getters

3.3、music-list
实现效果:歌手详情页的歌单主要实现了歌单向上滚动时,这个歌单也跟着滚动上去,且背景图逐渐变得灰暗,歌单向下滚动时,背景图逐渐清晰,放大,
实现思维:主要是监听歌单的滚动事件,拿到各个变化的点
1、通过实时获取的scrollY与背景图片的比值。从而得到图片放大的比例,以及图片模糊的opacity值

scrollY(newY){
let translateY = Math.max(this.minTranslateY, newY) //设置minTranslateY值,用来限制歌单只能滚动到离顶部一段距离 this.minTranslateY = -this.imgHeight + RESERVE_HEIGHT(40)
let zIndex = 0 //滚动过程中需要有层级的切换
let scale = 1
let blur = 1
const percent = Math.abs(newY / this.imgHeight)
if (newY > 0) {
scale = 1 + percent
zIndex = 10
} else {
blur = Math.max(0.2, 1-percent)
}
this.$refs.bgLayer.style[‘transform’] = `translate3d(0, ${translateY}px,0)`
this.$refs.bgImage.style[‘opacity’] = `${blur}`
if(newY < translateY){
this.$refs.bgImage.style.paddingTop = 0
this.$refs.bgImage.style.height = `${RESERVE_HEIGHT}px`
zIndex = 10
} else {
this.$refs.bgImage.style.paddingTop = ‘70%’
this.$refs.bgImage.style.height = 0
}
this.$refs.bgImage.style[‘transform’] = `scale(${scale})`
this.$refs.bgImage.style.zIndex = zIndex
}

此操作中涉及到css的transform的设置。考虑到浏览器对其兼容性不一,封装一个prefixStyle自动加上浏览器对应的前缀

let elementStyle = document.createElement(‘div’).style

let vendor = (() => {
let transformNames = {
webkit: ‘webkitTransform’,
Moz: ‘MozTransform’,
O: ‘OTransform’,
ms: ‘msTransform’,
standard: ‘transform’
}

for (let key in transformNames) {
if (elementStyle[transformNames[key]] !== undefined) return key
}
return false
})()

export function prefixStyle(style) {
if (vendor === false) return false
if (vendor === ‘standard’) return style
return vendor + style.charAt(0).toUpperCase() + style.substr(1)
}

4、播放器 player组件
播放器是本次实战的难点及重点,把播放器组件放在 App.vue 下,因为它是一个跟任何路由都不相关的东西。在任何路由下,它都可以去播放。切换路由并不会影响播放器的播放。

4.1 vuex设计
由于点击 详情页,以及搜索等 都可以进行播放歌曲,且播放组件在哪一个路由下都存在,故对其相关状态进行vuex管理

playing: false, //当前是否正在播放
fullScreen: false, //全屏属性
playlist: [], //为实现下一首,上一首功能,保存当前播放列表
sequenceList: [], //当前按正常顺序的列表,列表还有一种随机列表
mode: playMode.sequence, //播放模式。其三种放到配置文件config中
currentIndex: -1 //当前歌曲的index

4.2 展开收起动画
全屏和底部之间的切换加上一些动画切换效果,引入插件‘create-keyframe-animation’
实现效果:该动画是点到点之间位移且慢慢放大的缩放效果
实现思维:获取两个点的位置,利用css3的translate3d属性进行位移和scale的设置,结合transition提供动画的四个钩子函数(@enter,@after-enter,@leave,@after-leave)以及插件create-keyframe-animation来做缓动的动画效果
1. 获取mini-player对应位置(x,y)以及scale

_getPosAndScale() {
const targetWidth = 40
const paddingLeft = 40
const paddingBottom = 30
const paddingTop = 80
const width = window.innerWidth * 0.8
const scale = targetWidth / width
const x = -(window.innerWidth / 2 – paddingLeft)
const y = window.innerHeight – paddingTop – width / 2 – paddingBottom
return {
x,y,scale
}
},

4.2 调用插件做动画
import animations from ‘create-keyframe-animation’ //引入

const {x, y, scale} = this._getPosAndScale() //在method里的enter()钩子中调用
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)

4.3 切换播放模式
实现思维:播放模式三种:sequence(顺序播放)、loop(循环播放)、random(随机播放),默认是sequence,主要难点在与random播放,需要打乱播放列表。
打乱洗牌算法:遍历数组,且每次在0-数组长度 内获取一个随机数。将随机数的值与遍历值互换,

function getRandomInt(min, max) {
return Math.floor(Math.random() * (max – min + 1) + min)
}

export function shuffle(arr) {
let _arr = arr.slice()
for (let i = 0; i < _arr.length; i++) {
let j = getRandomInt(0, i)
let t = _arr[i]
_arr[i] = _arr[j]
_arr[j] = t
}
return _arr
}

当打乱了播放数组后。为了要保持当前播放的歌曲不变。那么currentIndex也要相应改变

_resetCurrentIndex(list) { //这里传入打乱的list
let index = list.findIndex((item) => {
return item.id == this.currentSong.id
})
this.setCurrentIndex(index)
},

4.4 歌词
获取歌词
获取歌词的接口也需要绕过refer,用axios服务器去拿qq的服务器,其设置接口如下:
axios.get(url, {
headers: {
referer: ‘https://c.y.qq.com/’,
host: ‘c.y.qq.com’
},
params: req.query
}).then((response) => { //ps:这里返回的response依然为jsonp格式,故需要对数据处理成json数据
let ret = response.data
if (typeof ret === ‘string’) {
const reg = /^\w+\(({.+})\)$/
const matches = ret.match(reg)
if (matches) {
ret = JSON.parse(matches[1])
}
}
res.json(ret)
})

获取到的歌词是base64格式,引入js-base64库对其进行解码
2. 歌词滚动
当前歌曲的歌词高亮是利用 js-lyric 会派发的 handle 事件

this.currentLyric = new Lyric(lyric, this.handleLyric)

js-lyric 会在每次改变当前歌词时触发这个函数,函数t提供的参数为:当前歌词的 lineNum 和 txt
为了当前高亮歌词保持最中间 是利用了 BScroll 滚动至高亮的歌词

handleLyric({lineNum, txt}) {
this.currentLineNum = lineNum
if(lineNum > 5){
this.$refs.lyricList.scrollToElement(this.$refs.lyricLine[lineNum – 5],0,1000) //lyricLine代表每一句歌词 ,lyricList 为包裹歌词的content
}else{
this.$refs.lyricList.scrollTo(0, 0, 1000)
}
},

4.5 运用mixins
由于底部可能是会有播放min-player,导致页面的滚动区域少了60px,这是一个公共的问题。在其他很多页面都会出现。故选择使用mixins处理机制
1.实现思路: handlePlaylist方法在调用页面处理程序实现滚动区域的bottom值,设置content的bottom。然后进行刷新scroll。在mixin文件中也设置一个handlePlaylist方法。当调用mixin的页面没有handlePlaylist方法时候。就会这行mixin里面得handlePlaylist进行报错。

mounted() {
this.handlePlaylist(this.playlist)
},
activated() {
this.handlePlaylist(this.playlist)
},
watch: {
playlist(newVal) {
this.handlePlaylist(newVal)
}
},
methods: {
handlePlaylist() {
throw new Error(‘component must implement handlePlaylist method’)
}
}

调用mixins页面的handlePlaylist方法

handlePlaylist(playList) {
const bottom = playList.length > 0 ? ’60px’ : ‘0’
this.$refs.singer.style.bottom = bottom
this.$refs.list.refresh()
},

5、search页面
5.1 搜索结果上拉加载
上拉加载数据需要根据接口的参数设置来一页页拿数据,该接口的参数设计如下

w: query //搜索内容
p: page //拿搜索的第几页数据了
perpage:perpage //每一页返回的数据条数
catZhida:zhida ? 1 : 0 //是否进行搜索歌手

实现关键代码
1. 拓展scroll组件,加上pullup属性来监听是否滑到了底部,滑到了底部就派发scrollToEnd事件

this.scroll.on(‘scrollEnd’, () => {
if(this.scroll.y <= (this.scroll.maxScrollY + 50)){ //增加50的buffer
this.$emit(‘scrollToEnd’)
}
})

监听scrollToEnd事件,执行加载更多searchMore事件,并在每次调用search接口的时候通过传回来的数据判断是否还有下一页数据
//调用seacrh接口
search(query) {
search(query, this.page, this.showSinger, perpage).then((res) => {
if(res.code === ERR_OK){
this.result = this.result.concat(this._getResult(res.data))
this._checkMore(res.data)
}
})
},
//并判断是否有下一页数据
_checkMore(data) {
const song = data.song
if (!song.list.length || (song.curnum + song.curpage * perpage) >= song.totalnum) {
this.hasMore = false
}
},
//每次搜索时候。初次调用search
searchFirst(){
this.page = 1
this.result = []
this.hasMore = true
this.search(this.query)
},
//下拉加载更多
searchMore(){
if(!this.hasMore) {
return
}
this.page++
this.search(this.query)
},

5.2 搜索历史
将每次搜索的关键词存入搜索历史,且页面刷新了,搜索历史还在,搜索历史列表数量固定在15个以内,且排在第一个的必须是最新搜索的关键词,还有对搜索历史的单个删除和批量删除作用。
实现关键代码
1. 引入good-storage库,该库对localstorage进行简单的封装。可以直接存入数组。提供了set,get方法
2. 存入关键词,由于要多次调用这个方法,故封装一个insertArray方法

function insertArray(arr, val, compare, maxLen) {
const index = arr.findIndex(compare)
if(index === 0){ //传入搜索词在第一个位置。arr不做任何变化
return
}
if(index > 0){ //传入搜索词存在且不是第一个位置,先删掉旧的位置上的数据,再把搜索词插入数组头部
arr.splice(index, 1)
}
arr.unshift(val)
if (maxLen && arr.length > maxLen) { //最大值限定
arr.pop()
}
}
//将插入后的新数组写入localstorage,并返回新数组供外部调用给vuex状态管理
export function saveSearch(query){
let searches = storage.get(SEARCH_KEY, [])
insertArray(searches, query, (item) => {
return item === query
},SEARCH_MAX_LEN)
storage.set(SEARCH_KEY, searches)
return searches
}

批量删除,作为一个比较严重的“大操作”,这里应需要提示用户是否确认真的要删除全部,故加上一个“确认框”。
methods: {
hide() { //往外提供show,hide方法供调用是否显示该确认框
this.showFlag = false
},
show() {
this.showFlag = true
},
confirm() { //派发出确认(confirm)以及取消(cancel)事件
this.hide()
this.$emit(‘confirm’)
},
cancel() {
this.hide()
this.$emit(‘cancel’)
}
}
}

六、编译打包上线
将项目通过npm run build 编译成一个dist文件目录,可以搭一个简单的node服务器跑起来。可以采用express框架结合axios。
配置prod.server.js

对项目的优化:对组件进行异步加载处理

const UserCenter = (resolve) => {
import(‘components/user-center/user-center’).then((module) => {
resolve(module)
})
}

移动端调试利器:vconsole
项目github:https://github.com/caoyanyuan/vue-player

七 、疑难总结 & 小技巧
6.1.关于 Vue
v-html可以转义字符。处理某些带有html的数据
watch 对象可以得到某个属性每次变化的新值
created()里面定义的数据只是初始化。而不会像data()那样给数据添加getter()和setter()方法从而监测数据变化。
mounted 是先触发子组件的 mounted,再会触发父组件的 mounted,但是对于 created 钩子,又会先触发父组件,再触发子组件。
如果组件有计数器,在组件销毁时期要记得清理,
对于 Vue 组件,this.refs.xxx拿到的是Vue实例,所以需要再通过refs.xxx拿到的是Vue实例,所以需要再通过el 拿到真实的 dom

6.2关于 JS
setTimeout(fn, 20)
一般来说 JS 线程执行完毕后一个 Tick 的时间约17ms内 DOM 就可以渲染完毕所以课程中 setTimeout(fn, 20) 是非常稳妥的写法
audio 提供的API
<audio ref=”audio” src=“” @play=”ready” @error=”error”@timeupdate=”updateTime” @ended=”end”></audio>

@play :当src资源拿到了之后执行的事件
使用场景:songReady作为一首歌可以播放的标志位,可以解决”如果未拿到歌曲资源,就进行播放造成的DOM报错“

@play=‘ready’
ready() {
this.songReady = true
}

@timeupdate:拿到当前歌曲的播放时间
使用场景:获取当前时间来做对应的滚动条进度显示

@timeupdate=”updateTime”
updateTime(e) {
this.currentTime = e.target.currentTime
},

currentTime:属性,拿到或者设置当前播放到的时间点
使用场景:currentTime为进度条拖拽到一个点,对应这个点的时间刻。将currentTime设置给audio,即可把歌曲对应播放到这个时间刻

this.$refs.audio.currentTime = currentTime //设置

@ended:歌曲播放结束后发生的事件
使用场景:播放完了、进行播放到下一首,或是如果在循环单曲模式下,继续循环这一首

end() {
if (this.mode === playMode.loop) {
this.loop()
} else {
this.next()
}
},

6.3 关于 webpack
” ~ ” 使 SCSS 可以使用 webpack 的相对路径
@import “~common/scss/mixin”;
@import “~common/scss/variable”;
babel-runtime 会在编译阶段把 es6 语法编译的代码打包到业务代码中,所以要放在dependencies里。
Fast Click 是一个简单、易用的库,专为消除移动端浏览器从物理触摸到触发点击事件之间的300ms延时
为什么会存在延迟呢?
从触摸按钮到触发点击事件,移动端浏览器会等待接近300ms,原因是浏览器会等待以确定你是否执行双击事件
何时不需要使用FastClick
FastClick 不会伴随监听任何桌面浏览器
Android 系统中,在头部 meta 中设置 width=device-width 的Chrome32+ 浏览器不存在300ms 延时,所以,也不需要
<meta name=”viewport” content=”width=device-width, initial-scale=1″>

同样的情况也适用于 Android设备(任何版本),在viewport 中设置 user-scalable=no,但这样就禁止缩放网页了
IE11+ 浏览器中,你可以使用 touch-action: manipulation; 禁止通过双击来放大一些元素(比如:链接和按钮)。IE10可以使用 -ms-touch-action: manipulation

————————————————
版权声明:本文为CSDN博主「cyyeel」的原创文章。
原文链接:https://blog.csdn.net/Forever201295/article/details/80266600

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/167299.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 2021.4激活码(破解版激活)

    2021.4激活码(破解版激活),https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • android activitymanagerservice_安卓开发API

    android activitymanagerservice_安卓开发APIAndroid中Java层的ActivityManager类中封装了很多API,可以供我们查询当前系统的很多信息,包括:内存、进程(Process)、任务栈(Task)、服务(Service)等的相关信息。利用这些信息可以进行一些有用的判断,例如判断当前系统内存是否不足、指定Service是否在运行中。(ActivityManager类封装了很多API方法供上层调用,具体负责管理Activity、Service等组件的是ActivityManagerService(AMS…

  • 唯一索引和普通索引的区别

    唯一索引和普通索引的区别一、背景介绍索引用来快速地寻找那些具有特定值的记录,如果没有索引,执行查询时Mysql必须从第一个记录开始扫描整个表的所有记录,直至找到符合要求的记录,表里面的记录数量越多,这个操作的代价就越高,如果作为搜索条件的列上已经创建了索引,mysql无需扫描任何记录即可迅速得到目标记录所在的位置。如果表有一千个记录,通过索引查找记录至少要比顺序扫描记录快100倍。所以对于现在的各种大型数据库来说,索…

  • Python实现web聊天室「建议收藏」

    Python实现web聊天室「建议收藏」使用Python模块中的select模块实现web聊天室功能select模块Python中的select模块专注于I/O多路复用,提供了selectpollepoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)参数:可接受四个参数(前三个必须)r…

  • Java四舍五入保留两位小数

    Java四舍五入保留两位小数文章目录Java四舍五入保留两位小数一、前言环境二、正文BigDecimalDecimalFormatMathcommons-math3String#formatJava四舍五入保留两位小数一、前言环境开发工具:IntelliJIDEAJDK:1.8BigDecimal:https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.htmlDecimalFormat:https://docs.oracle.com/java

  • Eureka 集群部署

    Eureka 集群部署Eureka集群介绍 注册中心Eureka又称服务中心,管理各种服务功能包括服务的注册、发现、熔断、负载、降级等。任何一个服务都不能直接去掉用,都需要通过注册中心来调用。通过服务中心来获取服务你不需要关注你调用的项目IP地址,由几台服务器组成,每次直接去服务中心获取可以使用的服务去调用既可。由于各种服务都注册到了服务中心,就有了很多高级功能条件。比如几台服务提供相同服务来做客户端负载均衡(Ribbon);监控服务器调用成功率来做断路器(Hystrix),移除服务列表中的故障点;监控服务调用时间

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号