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

项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star。


一、歌手页面布局与设计
  • 需求:联系人列表形式、左右联动的滚动列表、顶部标题随列表滚动而改变
歌手列表 快速入口列表
二、歌手数据接口抓取
  • api目录下创建singer.js——同recommend.js,依赖jsonp和一些公共参数

    import jsonp from '@/common/js/jsonp'
    import {commonParams, options} from '@/api/config' export function getSingerList() {
    const url = 'https://c.y.qq.com/v8/fcg-bin/v8.fcg' const data = Object.assign({}, commonParams, {
    channel: 'singer',
    page: 'list',
    key: 'all_all_all',
    pagesize: 100,
    pagenum: 1,
    hostUin: 0,
    needNewCode: 0,
    platform: 'yqq',
    g_tk: 1664029744,
    }) return jsonp(url, data, options)
    }
  • singer.vue —— 数据结构与需求不同:需要两层数组结构
  1. 第一层数组:将所有歌手以姓名开头字母Findex——ABCD顺序排列
  2. 第二层数组:在每一个字母歌手数组中,按顺序再将歌手进行排列
  3. 热门数据:简单将前十条数据取出来
三、歌手数据处理和inger类的封装
  • 定义_normalizeSinger()方法,规范化singer数据,接收参数list,即数据singers

    const HOT_NAME = '热门'
    const HOT_SINGER_LEN = 10 _normalizeSinger(list){
    let map = {
    hot: {
    title: HOT_NAME,
    items: []
    }
    }
    list.forEach((item, index) => {
    if(index < HOT_SINGER_LEN) {
    map.hot.items.push({
    id: item.Fsinger_mid,
    name: item.Fsinger_name,
    avatar: `https://y.gtimg.cn/music/photo_new/T001R300x300M000${item.Fsinger_mid}.jpg?max_age=2592000`
    })
    }
    //根据Findex作聚类
    const key = item.Findex
    if(!map[key]) {
    map[key] = {
    title: key,
    items: []
    }
    }
    map[key].items.push({
    id: item.Fsinger_mid,
    name: item.Fsinger_name,
    avatar: `https://y.gtimg.cn/music/photo_new/T001R300x300M000${item.Fsinger_mid}.jpg?max_age=2592000`
    })
    }) // console.log(map) }

    问题:avatar需要的数据是通过id计算得到的,且重复多次,重复代码太多

  • common->js目录下创建singer.js: 用面向对象的方法,构造一个Singer类

    export default class Singer {
    constructor({id, name}) {
    this.id = id
    this.name = name
    this.avatar = `https://y.gtimg.cn/music/photo_new/T001R300x300M000${id}.jpg?max_age=2592000`
    }
    }
  1. JavaScript constructor 属性返回对创建此对象的数组函数的引用

  2. 语法:object.constructor

  • 引入Singer:

    import Singer from '@/common/js/singer'

    使用new Singer({
                id: item.Fsinger_mid,
                name: item.Fsinger_name
    })代替前面的大段代码,减少avatar这样重复的大段代码

  • 为了得到有序列表,需要处理map

    let hot = []   //title是热门的歌手
    let ret = [] //title是A-Z的歌手
    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)
    }
    }
  • 为ret数组进行A-Z排序
    ret.sort((a, b) => {
    return a.title.charCodeAt(0) - b.title.charCodeAt(0)
    })
  • 最后将ret数组拼接在hot数组后返回

    return hot.concat(ret)

四、类通讯录的组件开发——滚动列表实现
  • base->listview目录下:创建listview.vue
  1. 引用scroll组件,在<scroll>根标签中传入data数据,当data发生变化时,强制BScroll重新计算
  2. props参数:
    props:{
    data: {
    type: Array,
    default: []
    }
    }
  3. DOM布局:
    <scroll class="listview" :data="data">
    <ul>
    <li v-for="(group, index) in data" :key="index" class="list-group">
    <h2 class="list-group-title">{{group.title}}</h2>
    <ul>
    <li v-for="(item, index) in group.items" :key="index" class="list-group-item">
    <img :src="item.avatar" class="avatar">
    <span class="name">{{item.name}}</span>
    </li>
    </ul>
    </li>
    </ul>
    </scroll>
  • singer.vue中:
  1. 引入并注册listview组件,传入参数data,绑定singers数据

    <div class="singer">
    <listview :data="singers"></listview>
    </div>
  2. 修改_getSingerList()中的singers为重置数据结构后的singers
    this.singers = this._normalizeSinger(res.data.list)
  3. 优化:使用图片懒加载技术处理<img>,:src替换为v-lazy
    <img v-lazy="item.avatar" class="avatar">
五、类通讯录的组件开发——右侧快速入口实现

获得title的集合数组

  • listview.vue中通过computed定义shortcutList()

    computed: {
    shortcutList() { //得到title的集合数组,‘热门’取1个字
    return this.data.map((group) => {
    return group.title.substr(0, 1)
    })
    }
    }
  1. 在<scroll>内层,与歌手列表同级编写布局DOM:

    <div class="list-shortcut">
    <ul>
    <li v-for="(item, index) in shortcutList" :key="index" class="item">{{item}}</li>
    </ul>
    </div>
  2. CSS样式:
    .list-shortcut
    position: absolute //绝对定位到右侧
    right: 0
    top: 50%

实现点击定位

  • 关键:监听touchstart事件
  1. 为<li class="item">扩展一个属性变量 :data-index="index"
  2. 在dom.js中封装一个getData函数,得到属性data-val的值
    export function getData(el, name, val){
    const prefix = 'data-'
    name = prefix + name
    if(val){
    return el.setAttribute(name, val)
    }else{
    return el.getAttribute(name)
    }
    }
  3. 在scroll.vue中扩展两个方法:
    scrollTo() {
    // 滚动到指定的位置;这里使用apply 将传入的参数,传入到this.scrollTo()
    this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
    },
    scrollToElement() {
    // 滚动到指定的目标元素
    this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
    }
  4. listview.vue中:引入getData方法
    import {getData} from '@/common/js/dom'

    <scroll>根标签中添加引用: ref="listview";<li class="list-group">中添加引用:ref="listGroup"

  5. 给快速入口列表添加touchstart事件:
    <div class="list-shortcut" @touchstart="onShortcutTouchStart">
    onShortcutTouchStart(e) {
    let anchorIndex = getData(e.target, 'index')//获取data-index的值 index
    _scrollTo(anchorIndex)
    } _scrollTo(index){
    this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0)//列表滚动定位
    }

实现滑动联动

  • 关键:监听touchmove事件
  1. 需求:滑动右侧快速入口列表,左侧歌手列表随之滚动
  2. 坑:快速入口列表下方就是歌手列表,同样可以滚动,需要避免滑动快速入口列表时,也使歌手列表受到影响
  3. 解决:阻止事件冒泡,阻止浏览器的延伸滚动 @touchmove.stop.prevent
  • 思路:

    在touchstart事件触发时,记录touch处的y值y1和anchorIndex,存储到this.touch对象中
    在touchmove事件触发时,同样记录touch处的y值y2,计算(y2-y1)/每个列表项的像素高 | 0 向下取整,
    得到两次touch位置列表项的差值delta,使touchmove时的anchorIndex = touch对象的anchorIndex + delta
    调用封装好的_scrollTo方法,传入anchorIndex,使歌手列表滚动到对应位置
  • 实现:
    const ANCHOR_HEIGHT = 18 //通过样式设置计算得到
    created() {
    this.touch = {} //在created中定义touch对象,而不在data或computed中定义,是因为touch对象不用进行监测
    },
    methods: {
    onShortcutTouchStart(e) {
    let anchorIndex = getData(e.target, 'index')//获取data-index的值 index 得到的是字符串
    let firstTouch = e.touches[0]
    this.touch.y1 = firstTouch.pageY
    this.touch.anchorIndex = anchorIndex
    this._scrollTo(anchorIndex)
    },
    onShortcutTouchMove(e) {
    let firstTouch = e.touches[0]
    this.touch.y2 = firstTouch.pageY
    let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0 //获取列表项差值,| 0 向下取整 = Math.floor()
    let anchorIndex = parseInt(this.touch.anchorIndex) + delta
    this._scrollTo(anchorIndex)
    },
    _scrollTo(index){
    //第二个参数表示:要不要滚动动画缓动时间; 0 瞬间滚动
    this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0)//列表滚动定位
    }
    }
  1. 坑:在touchstart时通过getData获得的anchorIndex是字符串,如果直接和delta相加得到的还是字符串,这样滚动的位置就不对
  2. 解决:
    let anchorIndex = parseInt(this.touch.anchorIndex) + delta

实现联动效果

  • 需求:滚动歌手列表时,快速入口列表对应的title项高亮显示
  • 思路:
    监听scroll事件,拿到pos对象,定义一个变量scrollY,【实时记录】歌手列表Y轴滚动的位置pos.y,
    监测数据data,每次发生改变时,都重新计算每个group元素的高度height,存在listHeight数组中
    监测scrollY,保留计算高度后的listHeight数组,遍历得到每个group元素的【高度区间】上限height1和下限height2,
    对比scrollY和每个group元素的高度区间height2-height1,确定当前滚动位置【currentIndex】,映射到DOM中
  • scroll.vue中:
  1. 添加一个props参数,决定要不要监听BScroll的滚动事件scroll

    listenScroll: {
    type: Boolean,
    default: false
    }
  2. _initScroll方法中:
    if(this.listenScroll) {
    let me = this //箭头函数中代理this
    this.scroll.on('scroll', (pos) => { //监听scroll事件
    me.$emit('scroll', pos) //派发一个scroll事件,传递pos位置对象:有x和y属性
    })
    }
  • listview.vue中:
  1. created()中添加两个属性值:

    this.listenScroll = true
    this.listHeight = []
  2. <scroll>根标签中传值 :listenScroll="listenScroll" 监听scroll事件 @scroll="scroll"
  • 常见习惯:私有方法如_scrollTo()一般放在下面,公共方法或绑定事件的方法如scroll()放在上面
  1. data中观测两个数据:

    scrollY: -1 //实时滚动的Y轴位置
    currentIndex: 0 //当前显示的第几个title项
  2. methods中添加scroll方法,传入接收的pos对象:
    scroll(pos) {
    this.scrollY = pos.y //实时获取BScroll滚动的Y轴距离
    }
  3. 添加_calculateHeight私有方法,计算每个group的高度height
    calculateHeight() {
    this.listHight = [] //每次重新计算每个group高度时,恢复初始值
    const list = this.$refs.listGroup
    let height = 0 //初始位置的height为0
    this.listHeight.push(height)
    for(let i=0; i<list.length; i++){
    let item = list[i] //得到每一个group的元素
    height += item.clientHeight //DOM元素可以用clientHeight获取元素高度
    this.listHeight.push(height) //得到每一个元素对应的height
    }
    }
  4. watch:{} 监测data的变化,使用setTimeout延时调用_calculateHeight,重新计算每个group的高度;监测scrollY的变化,遍历listHeight数组得到每个group元素的高度上限height1和下限height2;对比scrollY,确定当前滚动位置对应的title项currentIndex
     watch: {
    data() {
    setTimeout(() => { //使用setTimeout延时:因为数据的变化和DOM的变化还是间隔一些时间的
    this._calculateHeight()
    }, 20)
    },
    scrollY(newY) {
    const listHeight = this.listHeight
    //当滚动到顶部,newY>0
    if(newY > 0) {
    this.currentIndex = 0
    return
    }
    //在中间部分滚动,遍历到最后一个元素,保证一定有下限,listHeight中的height比元素多一个
    for(let i = 0; i < listHeight.length-1; i++){
    let height1 = listHeight[i]
    let height2 = listHeight[i+1]
    if(-newY >= height1 && -newY < height2) {
    this.currentIndex = i
    // console.log(this.currentIndex)
    return
    }
    }
    //当滚动到底部,且-newY大于最后一个元素的上限
    //currentIndex 比listHeight中的height多一个, 比元素多2个
    this.currentIndex = listHeight.length - 2
    }
    }
  • 坑:scroll组件中设置了probeType的默认值为1:滚动的时候会派发scroll事件,会截流,只能监听缓慢的滚动,监听不到swipe快速滚动
  • 解决:
  1. 需要在<scroll>中传递:probeType="3" 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
  2. 快速入口列表的title项<li class="item"> 动态绑定current class,将currentIndex映射到DOM中:
    :class="{'current': currentIndex === index}"
  3. CSS样式:
    &.current
    color: $color-theme
  • 坑:点击快速入口列表时,歌手列表会快速滚动,但点击的列表项没有高亮显示
  • 原因:高亮没有依赖点击的点,而是通过scrollY计算得到的,但目前_scrollTo中只是使列表滚动,没有派发scroll事件,改变scrollY
  • 解决:在_scrollTo中,手动改变scrollY的值,为当前元素的上限height

    this.scrollY = -this.listHeight[index]
  • 坑:touch事件都是加在父元素<div class="list-shortcut">上的,点击头尾--“热”“Z”之前和之后的边缘区块,会发现也是可以点击的,但它没有对应显示的歌手列表,这个点击是没有意义的
  • 解决:console.log(index)得知边缘区块的index都是null,在_scrollTo中设置如果是边缘区块,不执行任何操作,直接返回

    if(!index && index !== 0){
    return
    }
  • 坑:console.log(index)时发现滑动时滑到头部以上时是一个负值,滑到尾部以下时是一个很大的值
  • 原因:touchmove一直在执行,这个事件一直没有结束,它的Y值就会变大,这样算出来的delta加上之前的touch.anchorIndex得到的值就可能会超
  • 解决:在_scrollTo中处理index的边界情况

    if(index < 0){
    index = 0
    }else if(index > this.listHeight.length - 2){
    index = this.listHeight.length - 2
    }
  • 补充:scrollToElement(this.$refs.listGroup[index], 0)中的index没有出现问题,是因为BScroll中已经做了边界的处理
六、滚动固定标题实现——fixed title
  • 需求:当滚动到哪个歌手列表,顶部就显示当前歌手列表的title, 且固定不动,直到滚动到下一个歌手列表,再显示下一个title
  1. 布局DOM:当fixedTitle不为" "的时候显示

    <div class="list-fixed" v-show="fixedTitle">
    <div class="fixed-title">{{fixedTitle}}</div>
    </div>
  2. computed中计算fixedTitle:
    fixedTitle() {
    if(this.scrollY > 0){ //判断边界,‘热门’往上拉时,不显示
    return ''
    }
    //初始时,data默认为空,此时this.data[this.currentIndex]为undefinded
    return this.data[this.currentIndex] ? this.data[this.currentIndex].title : ''
    }
  3. CSS样式:
    .list-fixed
    position: absolute //绝对定位到顶部
    top: 0
    left: 0
    width: 100%
  • 坑:只有在歌手列表的title从底部穿过fixed title后,fixed title的内容才会发生改变,两个title没有过渡效果,体验不好
  • 解决:当歌手列表的title上边界滚动到fixed title下边界时,给fixed title添加一个上移效果,使两个title过渡顺滑
  1. 定义一个数据:

    diff: -1 //fixed title的偏移位置
  2. 在scrollY(newY)中实时得到diff: 
    this.diff = height2 + newY
    //得到fixed title上边界距顶部的偏移距离 = 歌手列表title height下限 + newY(上拉为负值)
  3. 给<div class="list-fixed">添加引用: ref="fixedTitle"
  4. 通过样式设置得到并定义fixed title的div高度: const TITLE_HEIGHT = 30
  5. 在watch:{}中观测diff:判断diff范围,数据改变DOM
    diff(newVal) {
    let fixedTop = (newVal>0 && newVal<TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0
    if(this.fixedTop === fixedTop){
    return
    }
    this.fixedTop = fixedTop
    this.$refs.fixedTitle.style.transform = `translate3d(0, ${fixedTop}px, 0)`
    }
  • 优化:listview歌手组件也是异步请求的数据,所以也加一个loading,引入loading组件注册
  1. 布局DOM:

    <div class="loading-container" v-show="!data.length">
    <loading></loading>
    </div>
  2. CSS样式:
     .loading-container
    position: absolute
    width: 100%
    top: 50%
    transform: translateY(-50%)

注:项目来自慕课网

【音乐App】—— Vue-music 项目学习笔记:歌手页面开发的更多相关文章

  1. 【音乐App】—— Vue-music 项目学习笔记:推荐页面开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 上一篇总结了项目概述.项目准备.页面骨架搭建.这一篇重点梳理推荐页面开发.项目github地址:https://github.com/66We ...

  2. 【音乐App】—— Vue-music 项目学习笔记:项目准备

    前言: 学习慕课网Vue高级实战课程后,在实践中总结一些这个项目带给自己的收获,希望可以再次巩固关于Vue开发的知识.这一篇主要梳理:项目概况.项目准备.页面骨架搭建.项目github地址:https ...

  3. 【音乐App】—— Vue-music 项目学习笔记:歌手详情页开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌曲列表 歌曲播放 一.子 ...

  4. 【音乐App】—— Vue-music 项目学习笔记:歌单及排行榜开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌单及详情页 排行榜及详情 ...

  5. 【音乐App】—— Vue-music 项目学习笔记:搜索页面开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 搜索歌手歌曲 搜索历史保存 ...

  6. 【音乐App】—— Vue-music 项目学习笔记:播放器内置组件开发(二)

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 播放模式切换 歌词滚动显示 ...

  7. 【音乐App】—— Vue-music 项目学习笔记:用户个人中心开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌曲列表 收藏歌曲 一.用 ...

  8. 【音乐App】—— Vue-music 项目学习笔记:歌曲列表组件开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 当前歌曲播放列表 添加歌曲 ...

  9. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

随机推荐

  1. 【SDOI2009】HH的项链 线段树

    题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义.HH 不断地收集新的贝壳,因此,他的项链变得越来越长. ...

  2. Halcon17 Linux 下载

    Halcon17 Linux 下载地址:http://www.211xun.com/download_page_10.html HALCON 17 是一套机器视觉图像处理库,由一千多个算子以及底层的数 ...

  3. Spring整合hibernate -hibernateTemplate

    目录 1 在Spring中初始化hibernateTemplate并注入Sessionfactory 2 DAO里注入hibernateTemplate 3 getHibernateTemplate. ...

  4. C#知识点<2>

    1. ? : 运算符(真2假3) 我们已经在前面的章节中讲解了 条件运算符 ? :,可以用来替代 if...else 语句.它的一般形式如下: Exp1 ? Exp2 : Exp3; 其中,Exp1. ...

  5. 软件包管理rpm_yum

    和文本相关的命令cat 正向显示文本tac 反向显示文本more 可以一步一步显示文本文件less 还可以往上看.几个快捷键:j(往下看), k (往上看), g(定位最上), G(定位最下), ct ...

  6. Arcengine 基本操作(待更新)

    /// <summary> /// 删除fieldName属性值为1的弧段 /// </summary> /// <param name="fieldName& ...

  7. [NOI2012][bzoj2879] 美食节 [费用流+动态加边]

    题面 传送门 思路 先看看这道题 修车 仔细理解一下,这两道题是不是一样的? 这道题的不同之处 但是有一个区别:本题中每一种车有多个需求,但是这个好办,连边的时候容量涨成$p\lbrack i\rbr ...

  8. Linux SCRT本地免秘钥登录远程机器

    一.生成本地公钥和私钥 1.1.创建公钥 步骤:工具->创建公钥 然后下一步: 秘钥类型选择RSA: 然后下一步: 密钥位长度:默认是1024,我这边是2048 然后下一步: 密钥格式: 然后点 ...

  9. Java数据结构-------List

    三种List:ArrayList,Vector,LinkedList 类继承关系图 ArrayList和Vector通过数组实现,几乎使用了相同的算法:区别是ArrayList不是线程安全的,Vect ...

  10. Dependency Injection in ASP.NET Web API 2

    What is Dependency Injection? A dependency is any object that another object requires. For example, ...