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>