技术栈
1,vue
2,vuex3,vue-router(子路由)需求分析1)歌手列表点击歌手会跳转到下级页面歌手详情页,歌手详情页由四个部分组成
歌手图片
返回按钮:点击返回歌手tab页随机播放按钮歌手歌曲滚动2)歌曲栏向上滚动到顶部时,图片和播放按钮随之隐藏,保留歌手名和返回按钮3)歌曲栏向下拖动时,歌手图片的高度作出无缝增高
新的组件
singer-detail.vuemusic-list.vuesong-list.vue
实现 1.vuex 在介绍子路由的实现前我们先来看vuex
这里写图片描述
在src文件夹下新建一个store文件夹,该文件夹下有多个js文件actions.js涉及的异步操作getters.js store中定义的getter这里返回state里的singerexport const singer = state => state.singerindex.js 组装模块并导出mutation.js 跟级别的mutation
//使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名import * as types from './mutation-types'const mutations = {[types.SET_SINGER](state, singer) {state.singer = singer}}export default mutationsmutation-type.js 对mutation作相应的映射state.js 定义store的state
1.子路由的实现
router文件夹下index.js配置歌手页的子路由{path: '/singer',component: Singer,children: [{path: ':id',component: SingerDetail}]}
singer.vue中使用vue-router
selectSinger(singer) {this.$router.push({path: `/singer/${singer.id}`})this.setSinger(singer)}
listview.vue歌手列表通过$emit方法传递出singer,这里的item代表每个singer
selectItem(item) {this.$emit('select', item)//console.log('item:' + item)}
2.歌手详情页的实现
歌手详情页实现用了以下组件
... ... ...
在Vue Devtools中可以清晰看到模板的结构。
singer.vue下有两个同级子组件分别为singer-detail和list-view,其中singer-detail组件是通过路由来切换的。 singer-detail组件下有music-list组件,这是歌手详情页组件,该组件下有scroll组件,scroll组件实现滚动,scroll组件下的song-list组件是歌手歌曲列表组件,loading组件是优化组件,当歌手歌曲还没有获取时显示正在加载,从而提高用户体验。数据的获取与传递
歌手对应歌曲数据获取
src/api/singer.js通过getSingerDetail方法获取歌曲export function getSingerDetail(singerId) {const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'const data = Object.assign({}, commonParams, {hostUin: 0,needNewCode: 0,platform: 'yqq',order: 'listen',begin: 0,num: 80,songstatus: 1,singermid: singerId})return jsonp(url, data, options)}
在singer-detail.vue组件中写了一个私有方法_getDetail用来获取歌手数据,singer.vue的data属性中维护了songs数组变量
data() {return {songs: [] }}_getDetail() {if (!this.singer.id) {this.$router.push('/singer')return}console.log(this.singer)getSingerDetail(this.singer.id).then((res) => {if (res.code === ERR_OK) {console.log(res.data.list)this.songs = this._normalizeSongs(res.data.list)}})}
_getDetail方法中使用了_normalizeSongs方法对请求到的数据进行处理,因为jsonp请求返回到的数据不是我们想要的,所以需要用此方法对数据进行处理。对请求返回到的数据返回一个songs数组,数组的每个元素代表每个song,其中song是一个类,这个类里面保留着歌曲的相关信息,如歌曲id,歌手姓名等等。
这是一个音乐app,后面还会涉及到歌曲的处理,所以出于代码可复用和可维护的角度来考虑,在common/js/song.js下创建了一个song Class,并创建了一个createSong工厂方法在传入参数后直接返回一个new song
_normalizeSongs(list) {let ret = []list.forEach((item) => {let {musicData} = itemif (musicData.songid && musicData.albummid) {ret.push(createSong(musicData))}})return ret}
common/js/song.js
export default class Song {constructor({id, mid, singer, name, album, duration, image, url}) {this.id = idthis.mid = midthis.singer = singerthis.name = namethis.album = albumthis.duration = durationthis.image = imagethis.url = url}}export function createSong(musicData) {return new Song({id: musicData.songid,mid: musicData.songmid,singer: filterSinger(musicData.singer),name: musicData.songname,album: musicData.albumname,duration: musicData.interval,image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${musicData.albummid}.jpg?max_age=2592000`,url: `http://ws.stream.qqmusic.qq.com/${musicData.songid}.m4a?fromtag=46`})}function filterSinger(singer) {let ret = []if (!singer) {return ''}singer.forEach((s) => {ret.push(s.name)})return ret.join('/')}
最后在singer-detail组件的created阶段获取数据
created() {this._getDetail()}
上面我介绍了如何获取歌手详情页组件的相关数据,这些数据最终的目的是用来渲染的。
歌手图片实现
在需求分析中提到的歌手图片,这是一张宽高比为10:7的图片,我们在之前的博文中提到了如何实现宽高比相等的图片,这里解决思路一样
music-list.vue中stylus.bg-imageposition: relativewidth: 100%height: 0padding-top: 70%transform-origin: topbackground-size: cover
歌手详情页歌手图片,歌手名,返回和播放按钮都是在music-list.vue中实现的(html,css),music-list下的子组件song-list维护了歌手歌曲列表。
点击图片返回效果实现,给按钮添加点击事件back
back() {this.$router.back()},
歌曲列表滚动效果实现
歌曲列表滚动效果使用了基础组件scroll组件,这里重点说明几个关键点,滚动中特效的实现利用了CSS3 transform的translate3d和scale方法
向上滚动实现图片隐藏效果
首先将scroll组件样式overflow:hidden去掉 给图片div层添加一个同级div class="bg-layer" music-list组件data维护了scrollY并在watch中设置监听,当scrollY发生改变时触发相应事件向下滚动实现图片放大(transform:scale)
具体代码重点scrollY
scrollY(newVal) {let translateY = Math.max(this.minTransalteY, newVal)let zIndex = 0let scale = 1let blur =0const percent = Math.abs(newVal / this.imageHeight)if (newVal > 0) {scale = 1 + percentzIndex = 10} else {blur = Math.min(20, percent * 20)}this.$refs.layer.style[transform] = `translate3d(0,${translateY}px,0)`this.$refs.filter.style[backdrop] = `blur(${blur}px)`if (newVal < this.minTransalteY) {zIndex = 10this.$refs.bgImage.style.paddingTop = 0this.$refs.bgImage.style.height = `${RESERVED_HEIGHT}px`this.$refs.playBtn.style.display = "none"} else {this.$refs.bgImage.style.paddingTop = '70%'this.$refs.bgImage.style.height = 0this.$refs.playBtn.style.display = ''}this.$refs.bgImage.style[transform] = `scale(${scale})`this.$refs.bgImage.style.zIndex = zIndex}
CSS3transform属性浏览器兼容处理
let elementStyle = document.createElement('div').stylelet 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)}
调测反思
在项目调测过程中,我们要结合Vue Devtools,比如我们在获得歌曲数组时在singer-detail中console.log(this.songs)会发现无法看到具体内容但在Vue Devtools中的singer-detail.vue组件的data中可以很清楚的看到songs数组
总结
子路由使用
滚动处理边界处理浏览器兼容性代码复用和可维护性