总结singer页面:
1.api中去获取 [‘热’,A-Z] 以及根据[‘热’,A-Z]获取的所有歌手的数据
2.渲染数据
- 2.1 渲染左边 字母title [‘热’,A-Z] + 该字母开头的歌手
因为存储字母的下标 与 存储对应字母开头歌手的下标 是一致的
[‘热’, A, B, C, D, E…]
[ ['热’门的所有的歌手], [字母A开头的歌手],[字母B开头的歌手],[字母C开头的歌手],[字母D开头的歌手]…]
直接循环存储对应字母开头歌手,获取index,根据存储字母数组的keys 以及 获取到的 index: keys[index]即可拿到对应的字母 - 2.2 渲染右边字母条 循环keys中的字母
3.使用 iscroll 包裹左边的内容,使其超出的内容可以滚动
4.点击右边字母条,左边可以滚动到指定的字母歌手区域
- 4.1 获取到[‘热’,A-Z]标题距离顶部的高度(offsetTop)根据顺序放进数组groupsTop
- 4.2 点击右边的字母条的字母,可以获取到对应的index,根据 groupsTop[index] 获取该字母的offsetTop
- 4.3 因为左侧的内容使用iscroll包裹,iscroll上有scrollTo,可以滚动到指定的高度
this.$refs.ScrollView.scrollTo(0, -offsetY)
5.滚动左侧的内容,使字母条对应的字母高亮
- 5.1 因为左侧的内容使用iscroll包裹,iscroll上有scrolling,可以监听滚动的高度,获取当前滚动的距离 y(负值)
- 5.3 循环数组groupsTop,获取 groupsTop[i],groupsTop[i+1],判断 groupsTop[i]< -y < groupsTop[i+1],获取到的i就是index
- 5.4 根据这个 index === keys的index 增加类名active使其高亮
6.左侧的字母标题吸顶效果
- 6.1 编写一个存放字母的元素(fixTitle),定位在顶部,根据5中当前滚动的距离y获取到对应的index
- 6.2 fixTitle中显示 keys[index]
7.当滚动到两个标题相触的时候,上一个标题有个被推出的移动效果
- 7.1 获取到标题的高度fixTitleHeight(offsetHeight)
- 7.2 下一组标题的偏移位 + 当前滚动出去的偏移位:diffOffsetY = nextTop + y
- 7.3 判断计算的结果是否是0~分组标题的高度的值 0 < diffOffsetY < fixTitleHeight
- 7.4 上面判断为true,将当前的fixTitle增加
this.$refs.fixTitle.style.transform = translateY(${fixTitleOffsetY}px)
- 7.5 判断条件为false,fixTitleOffsetY = 0
代码实现
// singer
<template>
<div>
<div class="singer">
<ScrollView ref="ScrollView">
<ul class="list-wrapper">
<li
class="list-group"
v-for="(item, index) in list"
:key="index"
ref="group"
>
<h2 class="group-title">{{ keys[index] }}</h2>
<ul>
<li class="group-item" v-for="obj in item" :key="obj.id + index">
<img v-lazy="obj.img1v1Url" alt="" />
<p>{{ obj.name }}</p>
</li>
</ul>
</li>
</ul>
</ScrollView>
<!-- A-Z导航 -->
<!-- <ul class="list-keys"> -->
<!-- <li -->
<!-- v-for="(key, index) in keys" -->
<!-- :key="key" -->
<!-- @click.stop="keyDown(index)" -->
<!-- :class="{ active: currentIndex === index }" -->
<!-- > -->
<!-- {{ key }} -->
<!-- </li> -->
<!-- </ul> -->
<ul class="list-keys">
<li
v-for="(key, index) in keys"
:key="key"
:data-index="index"
@touchstart.stop.prevent="touchstart"
@touchmove.stop.prevent="touchmove"
:class="{ active: currentIndex === index }"
>
{{ key }}
</li>
</ul>
<div class="fix-title" v-show="fixTitle !== ''" ref="fixTitle">{{fixTitle}}</div>
</div>
</div>
</template>
<script>
import { getAllArtists } from '../api/index'
import ScrollView from '../components/ScrollView.vue'
export default {
name: '',
components: {
ScrollView
},
data () {
return {
keys: [],
list: [],
groupsTop: [],
currentIndex: 0,
beiginOffsetY: 0,
moveOffsetY: 0,
scrollY: 0
}
},
methods: {
_keyDown (index) {
// 点击对应的字母,拿到index,根据groupsTop[index]获取到对应的距离顶部的高度
this.currentIndex = index
const offsetY = this.groupsTop[index]
this.$refs.ScrollView.scrollTo(0, -offsetY)
},
touchstart (e) {
// e.target.dataset.index 是字符串
const index = parseInt(e.target.dataset.index)
this._keyDown(index)
this.beiginOffsetY = e.touches[0].pageY
},
touchmove (e) {
// console.log('e', e.target)
this.MoveOffsetY = e.touches[0].pageY
const offsetY =
(this.moveOffsetY - this.beiginOffsetY) / e.target.offsetHeight
let index = parseInt(e.target.dataset.index) + Math.floor(offsetY)
if (index < 0) {
index = 0
} else if (index > this.keys.length - 1) {
index = this.keys.length - 1
}
this._keyDown(index)
}
},
computed: {
// 固定分组标题
fixTitle () {
// this.scrollY >= 0 表示当前处于最顶部的时候往下拖
if (this.scrollY >= 0) {
return ''
} else {
// 根据当前的currentIndex获取到当前的groupTitle
return this.keys[this.currentIndex]
}
}
},
created () {
getAllArtists()
.then(res => {
console.log(res)
this.keys = res.keys
this.list = res.list
})
.catch(err => {
console.log('err', err)
})
},
mounted () {
// 通过滚动的y值 去对比 group的offsetTop,获取当前的index
this.$refs.ScrollView.scrolling(y => {
this.scrollY = y
// console.log('y', y)
// 处理第一个区域
if (y >= 0) {
this.currentIndex = 0
return
}
// 处理中间区域
for (let i = 0; i < this.groupsTop.length - 1; i++) {
const preTop = this.groupsTop[i]
const nextTop = this.groupsTop[i + 1]
if (-y >= preTop && -y <= nextTop) {
this.currentIndex = i
// 当滚动到两个标题相触的时候,上一个标题有个被推出的移动效果
// 1.用下一组标题的偏移位 + 当前滚动出去的偏移位
const diffOffsetY = nextTop + y
let fixTitleOffsetY = 0
// 2.判断计算的结果是否是0~分组标题的高度的值
if (diffOffsetY >= 0 && diffOffsetY <= this.fixTitleHeight) {
fixTitleOffsetY = diffOffsetY - this.fixTitleHeight
} else {
fixTitleOffsetY = 0
}
if (fixTitleOffsetY === this.fixTitleOffsetY) {
return
}
this.fixTitleOffsetY = fixTitleOffsetY
this.$refs.fixTitle.style.transform = `translateY(${fixTitleOffsetY}px)`
return
}
}
// 处理最后一个区域
this.currentIndex = this.groupsTop.length - 1
})
},
watch: {
// 通过监听list,获取每个group的offsetTop
list () {
// 直接打印this.$refs.group为undefined,因为数据发生变化,数据可能没有渲染完成
// watch只能监听数据的变化,数据变化不一定已经渲染完了
// 为了保证是渲染完成之后再去获取,我们可以借助$nextTick方法来实现
// 也就是说在$nextTick回调函数中一定能拿到渲染完成之后的数据,因为.$nextTick的回调函数只有渲染完成之后才会执行
this.$nextTick(() => {
console.log(this.$refs.group)
this.$refs.group.forEach(group => {
// 获取所有group-title距离顶部的距离
this.groupsTop.push(group.offsetTop)
})
})
},
fixTitle () {
this.$nextTick(() => {
this.fixTitleHeight = this.$refs.fixTitle.offsetHeight
})
}
}
}
</script>
<style lang="scss" scoped>
@import "@/assets/css/mixin";
@import "@/assets/css/variable";
.singer {
position: fixed;
top: 184px;
left: 0;
right: 0;
bottom: 0;
@include bg_sub_color();
overflow: hidden;
.list-wrapper {
// width: 100%;
// height: 100%;
.list-group {
.group-title {
@include bg_color();
@include font_size($font_medium);
color: #fff;
padding: 10px 20px;
box-sizing: border-box;
}
.group-item {
display: flex;
justify-content: flex-start;
padding: 10px 20px;
border-bottom: 1px solid #ccc;
img {
width: 100px;
height: 100px;
border-radius: 50%;
overflow: hidden;
}
p {
@include font_size($font_medium);
@include font_color();
display: flex;
align-items: center;
margin-left: 20px;
}
}
}
}
.list-keys {
position: fixed;
right: 10px;
top: 60%;
transform: translate(-50%, -50%);
li {
@include font_color();
@include font_size($font_medium_s);
padding: 3px 0;
&.active {
text-shadow: 0 0 10px #000;
}
}
}
.fix-title {
position: absolute;
left: 0;
right: 0;
top: 0;
padding: 10px 20px;
box-sizing: border-box;
@include font_size($font_medium);
color: #fff;
@include bg_color()
}
}
</style>
IScroll-----ScrollView组件内容
<template>
<div id="wrapper" ref="wrapper">
<slot></slot>
</div>
</template>
<script>
// iscroll-probe专业版本,可以监听滚动的位置等细节
import IScroll from 'iscroll/build/iscroll-probe'
export default {
name: '',
mounted () {
this.iscroll = new IScroll(this.$refs.wrapper, {
mouseWheel: true,
scrollbars: false, // 是否显示滚动条
probeType: 3, // 像素级的触发scroll事件
// 解决拖拽卡顿问题
scrollX: false,
scrollY: true
// disablePointer: true,
// disableTouch: false
// disableMouse: true
})
// setTimeout(() => {
// // 数据是从网络上获取的,获取完需要重新计算滚动范围
// this.iscroll.refresh()
// }, 5000)
// 1.创建一个观察者对象
/**
* MutationObserver只要监听到指定内容发生变化,就会执行传入回调函数
* mutationList:发生变化的数组
* observer:观察者对象
*/
var observer = new MutationObserver((mutationList, observer) => {
// console.log(mutationList)
this.iscroll.refresh()
})
// 2.告诉观察者对象需要观察什么内容
const config = {
childList: true, // 观察目标子节点的变化,是否有添加或者删除
subtree: true, // 观察后代节点,默认为 false
attributeFilter: ['height', 'offsetHeight'] // 观察特定属性 子节点的高度
}
// 3.告诉观察者对象,我们需要观察谁,需要观察什么内容
/**
* 第一个参数:告诉观察者我们需要观察谁
* 第二个参数:告诉观察者我们需要观察什么内容
*/
observer.observe(this.$refs.wrapper, config)
},
methods: {
// 提供一个监听滚动距离的方法给外界使用
scrolling (Fn) {
this.iscroll.on('scroll', function () {
Fn(this.y) // 把当前偏移位给外界
})
},
refresh () {
setTimeout(() => {
this.iscroll.refresh()
}, 100)
},
// 滚动方法
scrollTo (x, y, time) {
this.iscroll.scrollTo(x, y, time)
}
}
}
</script>
<style lang="scss" scoped>
#wrapper {
width: 100%;
height: 100%;
}
</style>
api
// api
export const getHotArtists = () => {
return new Promise(function (resolve, reject) {
Network.get('top/artists?offset=0&limit=5')
.then(function (res) {
resolve(res.artists)
})
.catch(function (err) {
reject(err)
})
})
}
// 根据 ['热',A-Z] 获取对应的歌手
export const getLetterArtists = letter => {
return new Promise(function (resolve, reject) {
const letterArtists = []
Network.all([
Network.get(
`artist/list?offset=0&limit=5&type=1&area=7&initial=${letter}`
),
Network.get(
`artist/list?offset=0&limit=5&type=1&area=96&initial=${letter}`
),
Network.get(
`artist/list?offset=0&limit=5&type=2&area=7&initial=${letter}`
),
Network.get(
`artist/list?offset=0&limit=5&type=2&area=96&initial=${letter}`
),
Network.get(
`artist/list?offset=0&limit=5&type=3&area=7&initial=${letter}`
),
Network.get(
`artist/list?offset=0&limit=5&type=3&area=96&initial=${letter}`
)
])
.then(res => {
// console.log("res", res);
res.forEach(item => {
letterArtists.push(...item.artists)
})
// console.log('letterArtists', letterArtists)
resolve(letterArtists)
})
.catch(err => {
console.log(err)
})
})
}
// 获取 ['热',A-Z] 以及根据['热',A-Z]获取的所有歌手的数据
export const getAllArtists = letter => {
return new Promise(function (resolve, reject) {
const keys = ['热']
const list = [getHotArtists()]
// 先生成A-Z所有ASSCII
for (let i = 65; i < 91; i++) {
const char = String.fromCharCode(i)
// console.log('cahr', char)
keys.push(char)
list.push(getLetterArtists(char))
}
Network.all(list)
.then(res => {
const obj = {}
obj.keys = keys
obj.list = res
// console.log(res);
resolve(obj)
})
.catch(err => {
console.log(err)
reject(err)
})
console.log('cahr', keys)
})
}
网易云音乐的API
学习笔记,版权Jonathan所有