Vue3项目实战训练------音乐播放器(开源项目)(四)

本文详细介绍了如何在Vue3项目中实现一个音乐播放器的歌手列表页面,包括使用滚动组件、添加吸顶过渡效果以及构建索引列表。通过引入自定义滚动组件、利用Better Scroll库和Vue的计算属性及自定义事件,实现了列表的实时滚动监听和吸顶效果。同时,通过创建首字母索引并绑定触摸事件,实现了快速定位歌手列表的功能。
摘要由CSDN通过智能技术生成

Vue3项目自学训练------音乐播放器(开源项目)(四)

1. 将滚动组件应用在歌手列表页面

(滚动区域是要检测到有固定区域超出)

//在SingerList.vue中导入滚动组件
import MyScroll from "@/components/base/Scroll";
<template>
<!-- 在SingerList.vue中最外层的盒子换成滚动组件包裹 --> 
  <my-scroll class="singer-list">
    <ul class="view-scroll">
    </ul>
  </my-scroll>
</template>
<style lang="scss" scoped>
.singer {
  position: fixed;
  width: 100%;
  // 可视窗口的计算
  bottom: 0;
  top: 88px;
}
</style>

2. 吸顶索引及吸顶的过渡效果

SingerList.vue

	<div class="fixed">
      <div class="fixed-title"></div>
    </div>
.fixed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    .fixed-title {
        height: 30px;
        line-height: 30px;
        padding-left: 20px;
        font-size: $font-size-small;
        color: $color-text-l;
        background: $color-highlight-background;
    }
}
//useScroll.js
// 获取实时滚动距离
    export function useScroll(props, emit) {
    const rootRef = ref(null)
    const scroll = ref(null)

    onMounted(() => {
        scroll.value = new BScroll(rootRef.value, {
 observeDOM: true, //开启observe-dom插件,深度监听
            ...props
        })
// 获取实时滚动距离
        if(props.probeType > 0) {
// 外层时betterscroll对象,自带的
            scroll.value.on('scroll', (pos) => {
 // 触发父组件内的scroll自定义事件
                emit('scroll', pos)
            })
        }
    })
    return {
        rootRef
    }
import { useScroll } from "@/assets/js/useScroll";
//Scroll.vue
// 接受自定义事件
const emit = defineEmits(["scroll"])
const props = defineProps({
  probeType: {
    type: Number,
    default: 1,
  },
});
//传个emit的参数
const { rootRef } = useScroll(props, emit);

封装获取实时滚动

//useFixed.js
export default function useFixed() {
    const onScroll = (pos) => {
        console.log(pos);
      };

      return {
          onscroll
      }
}

SingerLIst接收

import MyScroll from "@/components/base/Scroll";
import useFixed from "@/assets/js/useFixed";

// 接收数据
const props = defineProps(["singerList"]);
const { onScroll } = useFixed();

在SingerList传一个groupRef

<ul class="view-scroll" ref="groupRef">
<li class="group" v-for="group in singerList" :key="group.tag">
</li></ul>
const { onScroll, groupRef } = useFixed(props);

useFixed.js接收

import {onMounted, ref} from 'vue'
export default function useFixed() {
    // 用于记录滚动距离
    const scrollY = ref(0)
    const groupRef = ref(null)
    const onScroll = (pos) => {
        // console.log(pos);
        scrollY.value = -pos.y
        console.log(scrollY.value);
        caculate()
      };

    //   计算li高度的事件处理函数
    function caculate() {
        console.log(groupRef.value);
    }

    // onMounted(() => {

    // })
      return {
          onscroll,
          groupRef
      }
}

3. 索引列表

在Singer.vue中绑定一个arrStr将索引数组导出

<template>
  <div class="singer">
    <my-singerList :singerList="singerList" :arrStr="arrStrUppercase"></my-singerList>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue";
import { getSingerList } from "@/service/singer";
import MySingerList from "@/components/SingerList";

const singerList = ref([]);
//创建26个大写字母的数组
const arrStrUppercase = computed(() => {
  let arr = ["热"];
  for (let i = 65; i < 91; i++) {
    // console.log(i);
    arr.push(String.fromCharCode(i));
  }
  // console.log(arr);
  return arr;
});

onMounted(async () => {
  // console.log(arrStrUppercase);
  const result = await getSingerList(arrStrUppercase.value);
  // console.log(result);
  singerList.value = result;
});
</script>

SingerList.vue接收

<template>
  <my-scroll class="singer-list" @scroll="onScroll" :probeType="3">
    <!-- 歌手展示列表 -->
    <ul class="view-scroll" ref="groupRef">
      <li class="group" v-for="group in singerList" :key="group.tag">
        <h2 class="title">{{ group.tag }}</h2>
        <ul>
          <li class="item" v-for="item in group.nameArr" :key="item.id">
            <div class="avatar">
              <img v-img-lazy="item.picUrl" alt="" />
            </div>
            <span class="name">{{ item.name }}</span>
          </li>
        </ul>
      </li>
    </ul>
    <!-- 吸顶索引 -->
    <div class="fixed" v-show="fixedTitle" :style="fixedStyle">
      <div class="fixed-title">{{ fixedTitle }}</div>
    </div>
    <!-- 首字母索引 -->
    <div class="shortcut">
      <ul>
        <li class="item" v-for="(item, index) in arrStr" :key="index">
          {{ item }}
        </li>
      </ul>
    </div>
  </my-scroll>
</template>

<script setup>
import MyScroll from "@/components/base/Scroll";
import useFixed from "@/assets/js/useFixed";

// 接收数据
const props = defineProps(["singerList", "arrStr"]);
const { onScroll, groupRef, fixedTitle, fixedStyle } = useFixed(props);
</script>

<style lang="scss" scoped>
</style>

4.代码

useShortcut.js

import {ref} from 'vue'
export default function useShortcut(groupRef) {
    const scrollRef = ref(null)

    // 注册触摸跳转对呀的li
    function onShortcutTouchStart(e) {
        // 获取触摸字母的index
        // console.log(e.target.dataset.index);
        const anchorIndex = e.target.dataset.index / 1
        scrollTo(anchorIndex)
    }

    function scrollTo(index) {
        console.log(scrollRef.value);
        // const scroll = scrollRef.value.scroll
        console.log(groupRef.value.children[index]);
        // const targetEl = groupRef.value.children[index]
        // 官方提供的方法
        // scroll.scrollToElement(targetEl)
    }

    return {
        scrollRef,
        onShortcutTouchStart
    }
}

useScroll.js

import BScroll from '@better-scroll/core'
import ObserveDOM from '@better-scroll/observe-dom'
import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
BScroll.use(ObserveDOM)

export function useScroll(props, emit) {
    const rootRef = ref(null)
    const scroll = ref(null)

    onMounted(() => {
        scroll.value = new BScroll(rootRef.value, {
            observeDOM: true, //开启observe-dom插件,深度监听
            ...props
        })
        // 获取实时滚动距离
        if(props.probeType > 0) {
            // 外层时betterscroll对象,自带的
            scroll.value.on('scroll', (pos) => {
                // 触发父组件内的scroll自定义事件
                emit('scroll', pos)
            })
        }
    })
    onUnmounted(() => {
        // 销毁
        scroll.value.destroy()
    })
    onActivated(() => {
        // 恢复功能 
        scroll.value.enable()
        // 刷新
        scroll.value.refresh()
    })
    onDeactivated(() => {
        // 失去功能
        scroll.value.disable()
    })

    return {
        rootRef
    }
}

Scroll.vue

<template>
  <div class="" ref="rootRef">
    <slot></slot>
  </div>
</template>

<script setup>
import { useScroll } from "@/assets/js/useScroll";

const props = defineProps({
  probeType: {
    type: Number,
    default: 1,
  },
});
// 接受自定义事件
const emit = defineEmits(["scroll"]);
const { rootRef, scroll } = useScroll(props, emit);

// 暴露超出给父组件使用
defineExpose({
  scroll,
});
</script>

<style lang="scss" scoped>
</style>

SingerList.vue

<template>
  <my-scroll
    class="singer-list"
    @scroll="onScroll"
    :probeType="3"
    ref="scrollRef"
  >
    <!-- 歌手展示列表 -->
    <ul class="view-scroll" ref="groupRef">
      <li class="group" v-for="group in singerList" :key="group.tag">
        <h2 class="title">{{ group.tag }}</h2>
        <ul>
          <li class="item" v-for="item in group.nameArr" :key="item.id">
            <div class="avatar">
              <img v-img-lazy="item.picUrl" alt="" />
            </div>
            <span class="name">{{ item.name }}</span>
          </li>
        </ul>
      </li>
    </ul>
    <!-- 吸顶索引 -->
    <div class="fixed" v-show="fixedTitle" :style="fixedStyle">
      <div class="fixed-title">{{ fixedTitle }}</div>
    </div>
    <!-- 首字母索引 -->
    <div class="shortcut">
      <ul>
        <li
          class="item"
          :class="{ current: currentIndex === index }"
          v-for="(item, index) in arrStr"
          :key="index"
          :data-index="index"
          @touchstart="onShortcutTouchStart"
        >
          <!-- 注册自定义属性:data-index="index" -->
          {{ item }}
        </li>
      </ul>
    </div>
  </my-scroll>
</template>

<script setup>
import MyScroll from "@/components/base/Scroll";
import useFixed from "@/assets/js/useFixed";
import useShortcut from "@/assets/js/useShortcut";

// 接收数据
const props = defineProps(["singerList", "arrStr"]);
const { onScroll, groupRef, fixedTitle, fixedStyle, currentIndex } = useFixed(props);
const { scrollRef, onShortcutTouchStart } = useShortcut(groupRef);
</script>

<style lang="scss" scoped>
.singer-list {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: $color-background;
  .group {
    padding-bottom: 30px;
    .title {
      height: 30px;
      line-height: 30px;
      padding-left: 20px;
      font-size: $font-size-small;
      color: $color-text-l;
      background: $color-highlight-background;
    }
    .item {
      display: flex;
      align-items: center;
      padding: 20px 0 0 30px;
      .avatar {
        position: relative;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        overflow: hidden;
        img {
          position: absolute;
          width: 130%;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
      }
      .name {
        margin-left: 20px;
        color: $color-text-l;
        font-size: $font-size-medium;
      }
    }
  }
  .fixed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    .fixed-title {
      height: 30px;
      line-height: 30px;
      padding-left: 20px;
      font-size: $font-size-small;
      color: $color-text-l;
      background: $color-highlight-background;
    }
  }
  .shortcut {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 20px;
    padding: 20px 0;
    border-radius: 10px;
    text-align: center;
    background: $color-background-d;
    font-family: Helvetica;
    .item {
      padding: 3px;
      line-height: 1;
      color: $color-text-l;
      font-size: $font-size-small;
      &.current {
        // 高亮当前区间的字母高亮
        // color: $color-theme;
        color: rgb(105, 105, 249);
      }
    }
  }
}
</style>

Singer.vue

<template>
  <div class="singer">
    <my-singerList :singerList="singerList" :arrStr="arrStrUppercase"></my-singerList>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue";
import { getSingerList } from "@/service/singer";
import MySingerList from "@/components/SingerList";

const singerList = ref([]);
//创建26个大写字母的数组
const arrStrUppercase = computed(() => {
  let arr = ["热"];
  for (let i = 65; i < 91; i++) {
    // console.log(i);
    arr.push(String.fromCharCode(i));
  }
  // console.log(arr);
  return arr;
});

onMounted(async () => {
  // console.log(arrStrUppercase);
  const result = await getSingerList(arrStrUppercase.value);
  // console.log(result);
  singerList.value = result;
});
</script>

<style lang="scss" scoped>
.singer {
  position: fixed;
  width: 100%;
  // 可视窗口的计算
  bottom: 0;
  top: 87px;
}
</style>

在这里插入图片描述

仿网易音乐VUE项目 ## 功能特性 1. 登录 2. 刷新登录 3. 发送验证码 4. 校验验证码 5. 注册(修改密码) 6. 获取用户信息 , 歌单,收藏,mv, dj 数量 7. 获取用户歌单 8. 获取用户电台 9. 获取用户关注列表 10. 获取用户粉丝列表 11. 获取用户动态 12. 获取用户播放记录 13. 获取精品歌单 14. 获取歌单详情 15. 搜索 16. 搜索建议 17. 获取歌词 18. 歌曲评论 19. 收藏单曲到歌单 20. 专辑评论 21. 歌单评论 22. mv 评论 23. 电台节目评论 24. banner 25. 获取歌曲详情 26. 获取专辑内容 27. 获取歌手单曲 28. 获取歌手 mv 29. 获取歌手专辑 30. 获取歌手描述 31. 获取相似歌手 32. 获取相似歌单 33. 相似 mv 34. 获取相似音乐 35. 获取最近 5 个听了这首歌的用户 36. 获取每日推荐歌单 37. 获取每日推荐歌曲 38. 私人 FM 39. 签到 40. 喜欢音乐 41. 垃圾桶 42. 歌单 ( 网友精选碟 ) 43. 新碟上架 44. 热门歌手 45. 最新 mv 46. 推荐 mv 47. 推荐歌单 48. 推荐新音乐 49. 推荐电台 50. 推荐节目 51. 独家放送 52. mv 排行 53. 获取 mv 数据 54. 播放 mv/视频 55. 排行榜 56. 歌手榜 57. 云盘 58. 电台 - 推荐 59. 电台 - 分类 60. 电台 - 分类推荐 61. 电台 - 订阅 62. 电台 - 详情 63. 电台 - 节目 64. 给评论点赞 65. 获取动态 66. 热搜列表(简略) 67. 发送私信 68. 发送私信歌单 69. 新建歌单 70. 收藏/取消收藏歌单 71. 歌单分类 72. 收藏的歌手列表 73. 订阅的电台列表 74. 相关歌单推荐 75. 付费精选接口 76. 音乐是否可用检查接口 77. 登录状态 78. 获取视频播放地址 79. 发送/删除评论 80. 热门评论 81. 视频评论 82. 退出登录 83. 所有榜单 84. 所有榜单内容摘要 85. 收藏视频 86. 收藏 MV 87. 视频详情 88. 相关视频 89. 关注用户 90. 新歌速递 91. 喜欢音乐列表(无序) 92. 收藏的 MV 列表 93. 获取最新专辑 94. 听歌打卡 95. 获取视频标签/分类下的视频 96. 已收藏专辑列表 97. 获取动态评论 98. 歌单收藏者列表 99. 云盘歌曲删除 100. 热门话题 101. 电台 - 推荐类型 102. 电台 - 非热门类型 103. 电台 - 今日优选 104. 心动模式/智能播放 105. 转发动态 106. 删除动态 107. 分享歌曲、歌单、mv、电台、电台节目到动态 108. 通知-私信 109. 通知-评论 110. 通知-@我 111. 通知-通知 112. 设置 113. 云盘数据详情 114. 私信内容 115. 我的数字专辑 116. batch批量请求接口 117. 获取视频标签列表 118. 全部mv 119. 网易出品mv 120. 收藏/取消收藏专辑 121. 专辑动态信息 122. 热搜列表(详细) 123. 更换绑定手机 124. 检测手机号码是否已注册 125. 初始化昵称 126. 更新歌单描述 127. 更新歌单名 128. 更新歌单标签 129. 默认搜索关键词 130. 删除歌单 131. 电台banner 132. 用户电台 133. 热门电台 134. 电台 - 节目详情 135. 电台 - 节目榜 136. 电台 - 新晋电台榜/热门电台榜 137. 类别热门电台 138. 云村热评 139. 电台24小时节目榜 140. 电台24小时主播榜 141. 电台最热主播榜 142. 电台主播新人榜 143. 电台付费精品榜 144. 歌手热门50首歌曲 145. 购买数字专辑 146. 获取 mv 点赞转发评论数数据 147. 获取视频点赞转发评论数数据 148. 调整歌单顺序 149. 调整歌曲顺序 150. 独家放送列表 151. 获取推荐视频 152. 获取视频分类列表 153. 获取全部视频列表接口 154. 获取历史日推可用日期列表 155. 获取历史日推详细数据 156. 国家编码列表 157. 首页-发现 158. 首页-发现-圆形图标入口列表 159. 数字专辑-全部新碟 160. 数字专辑-热门新碟 161. 数字专辑&数字单曲-榜单 162. 数字专辑-语种风格馆 163. 数字专辑详情 ## 环境要求 需要 NodeJS 8.12+ 环境
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.MUXIAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值