mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2024-11-25 09:41:49 +08:00
feat: virtual scrollbar
This commit is contained in:
parent
226a2145c4
commit
7c79afd0d1
29
src/App.vue
29
src/App.vue
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<div id="app" :class="{ 'user-select-none': userSelectNone }">
|
||||
<Scrollbar v-show="!showLyrics" ref="scrollbar" />
|
||||
<Navbar v-show="showNavbar" ref="navbar" />
|
||||
<main>
|
||||
<main ref="main" @scroll="handleScroll">
|
||||
<keep-alive>
|
||||
<router-view v-if="$route.meta.keepAlive"></router-view>
|
||||
</keep-alive>
|
||||
|
@ -22,6 +23,7 @@
|
|||
<script>
|
||||
import ModalAddTrackToPlaylist from './components/ModalAddTrackToPlaylist.vue';
|
||||
import ModalNewPlaylist from './components/ModalNewPlaylist.vue';
|
||||
import Scrollbar from './components/Scrollbar.vue';
|
||||
import Navbar from './components/Navbar.vue';
|
||||
import Player from './components/Player.vue';
|
||||
import Toast from './components/Toast.vue';
|
||||
|
@ -39,10 +41,12 @@ export default {
|
|||
ModalAddTrackToPlaylist,
|
||||
ModalNewPlaylist,
|
||||
Lyrics,
|
||||
Scrollbar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isElectron: process.env.IS_ELECTRON, // true || undefined
|
||||
userSelectNone: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -93,6 +97,9 @@ export default {
|
|||
this.$store.dispatch('fetchLikedMVs');
|
||||
}
|
||||
},
|
||||
handleScroll() {
|
||||
this.$refs.scrollbar.handleScroll();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -104,20 +111,18 @@ export default {
|
|||
}
|
||||
|
||||
main {
|
||||
margin-top: 84px;
|
||||
margin-bottom: 96px;
|
||||
padding: {
|
||||
right: 10vw;
|
||||
left: 10vw;
|
||||
}
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
padding: 64px 10vw 96px 10vw;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-width: 1336px) {
|
||||
main {
|
||||
padding: 0 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
padding: 64px 5vw 96px 5vw;
|
||||
}
|
||||
}
|
||||
|
|
173
src/components/Scrollbar.vue
Normal file
173
src/components/Scrollbar.vue
Normal file
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<div>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="show"
|
||||
id="scrollbar"
|
||||
:class="{ 'on-drag': isOnDrag }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div
|
||||
id="thumbContainer"
|
||||
:class="{ active }"
|
||||
:style="thumbStyle"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave"
|
||||
@mousedown="handleDragStart"
|
||||
@click.stop
|
||||
>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Scrollbar',
|
||||
data() {
|
||||
return {
|
||||
top: 0,
|
||||
thumbHeight: 0,
|
||||
active: false,
|
||||
show: false,
|
||||
hideTimer: null,
|
||||
isOnDrag: false,
|
||||
onDragClientY: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
thumbStyle() {
|
||||
return {
|
||||
transform: `translateY(${this.top}px)`,
|
||||
height: `${this.thumbHeight}px`,
|
||||
};
|
||||
},
|
||||
main() {
|
||||
return this.$parent.$refs.main;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$router.beforeEach((to, from, next) => {
|
||||
this.show = false;
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleScroll() {
|
||||
const clintHeight = this.main.clientHeight - 128;
|
||||
const scrollHeight = this.main.scrollHeight - 128;
|
||||
const scrollTop = this.main.scrollTop;
|
||||
let top = ~~((scrollTop / scrollHeight) * clintHeight);
|
||||
let thumbHeight = ~~((clintHeight / scrollHeight) * clintHeight);
|
||||
|
||||
if (thumbHeight < 24) thumbHeight = 24;
|
||||
if (top > clintHeight - thumbHeight) {
|
||||
top = clintHeight - thumbHeight;
|
||||
}
|
||||
this.top = top;
|
||||
this.thumbHeight = thumbHeight;
|
||||
|
||||
if (!this.show && clintHeight !== thumbHeight) this.show = true;
|
||||
this.setScrollbarHideTimeout();
|
||||
},
|
||||
handleMouseenter() {
|
||||
this.active = true;
|
||||
},
|
||||
handleMouseleave() {
|
||||
this.active = false;
|
||||
this.setScrollbarHideTimeout();
|
||||
},
|
||||
handleDragStart(e) {
|
||||
this.onDragClientY = e.clientY;
|
||||
this.isOnDrag = true;
|
||||
this.$parent.userSelectNone = true;
|
||||
document.addEventListener('mousemove', this.handleDragMove);
|
||||
document.addEventListener('mouseup', this.handleDragEnd);
|
||||
},
|
||||
handleDragMove(e) {
|
||||
if (!this.isOnDrag) return;
|
||||
const clintHeight = this.main.clientHeight - 128;
|
||||
const scrollHeight = this.main.scrollHeight - 128;
|
||||
const clientY = e.clientY;
|
||||
const scrollTop = this.main.scrollTop;
|
||||
const offset = ~~(
|
||||
((clientY - this.onDragClientY) / clintHeight) *
|
||||
scrollHeight
|
||||
);
|
||||
this.top = ~~((scrollTop / scrollHeight) * clintHeight);
|
||||
this.main.scrollBy(0, offset);
|
||||
this.onDragClientY = clientY;
|
||||
},
|
||||
handleDragEnd() {
|
||||
this.isOnDrag = false;
|
||||
this.$parent.userSelectNone = false;
|
||||
document.removeEventListener('mousemove', this.handleDragMove);
|
||||
document.removeEventListener('mouseup', this.handleDragEnd);
|
||||
},
|
||||
handleClick() {
|
||||
this.main.scrollBy({
|
||||
top: 256,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
},
|
||||
setScrollbarHideTimeout() {
|
||||
if (this.hideTimer !== null) clearTimeout(this.hideTimer);
|
||||
this.hideTimer = setTimeout(() => {
|
||||
if (!this.active) this.show = false;
|
||||
this.hideTimer = null;
|
||||
}, 4000);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#scrollbar {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 16px;
|
||||
z-index: 1000;
|
||||
|
||||
#thumbContainer {
|
||||
margin-top: 64px;
|
||||
div {
|
||||
transition: background 0.4s;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: rgba(128, 128, 128, 0.38);
|
||||
}
|
||||
}
|
||||
#thumbContainer.active div {
|
||||
background: rgba(128, 128, 128, 0.58);
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
#thumbContainer div {
|
||||
background: var(--color-secondary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
#scrollbar.on-drag {
|
||||
left: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-show="show" class="album">
|
||||
<div v-show="show" class="album-page">
|
||||
<div class="playlist-info">
|
||||
<Cover
|
||||
:id="album.id"
|
||||
|
@ -299,6 +299,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.album-page {
|
||||
margin-top: 32px;
|
||||
}
|
||||
.playlist-info {
|
||||
display: flex;
|
||||
width: 78vw;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-show="show" class="artist">
|
||||
<div v-show="show" class="artist-page">
|
||||
<div class="artist-info">
|
||||
<div class="head">
|
||||
<img :src="artist.img1v1Url | resizeImage(1024)" />
|
||||
|
@ -354,6 +354,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.artist-page {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.artist-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="explore">
|
||||
<div class="explore-page">
|
||||
<h1>{{ $t('explore.explore') }}</h1>
|
||||
<div class="buttons">
|
||||
<div
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div v-show="show" class="home">
|
||||
<div v-if="settings.showPlaylistsByAppleMusic !== false" class="index-row">
|
||||
<div
|
||||
v-if="settings.showPlaylistsByAppleMusic !== false"
|
||||
class="index-row first-row"
|
||||
>
|
||||
<div class="title"> by Apple Music </div>
|
||||
<CoverRow
|
||||
:type="'playlist'"
|
||||
|
@ -147,6 +150,9 @@ export default {
|
|||
.index-row {
|
||||
margin-top: 54px;
|
||||
}
|
||||
.index-row.first-row {
|
||||
margin-top: 32px;
|
||||
}
|
||||
.playlists {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-show="show">
|
||||
<div v-show="show" ref="library">
|
||||
<h1>
|
||||
<img class="avatar" :src="data.user.avatarUrl | resizeImage" />{{
|
||||
data.user.nickname
|
||||
|
@ -123,7 +123,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ContextMenu ref="playlistTabMenu">
|
||||
<ContextMenu>
|
||||
<div class="item" @click="changePlaylistFilter('all')">{{
|
||||
$t('contextMenu.allPlaylists')
|
||||
}}</div>
|
||||
|
@ -241,7 +241,7 @@ export default {
|
|||
return;
|
||||
}
|
||||
this.currentTab = tab;
|
||||
window.scrollTo({ top: 375, behavior: 'smooth' });
|
||||
this.$parent.$refs.main.scrollTo({ top: 375, behavior: 'smooth' });
|
||||
},
|
||||
goToLikedSongsList() {
|
||||
this.$router.push({ path: '/library/liked-songs' });
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<div class="login-container">
|
||||
<div class="section-1">
|
||||
<img src="/img/logos/netease-music.png" />
|
||||
</div>
|
||||
|
@ -88,6 +89,7 @@
|
|||
v-html="isElectron ? $t('login.noticeElectron') : $t('login.notice')"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -206,7 +208,14 @@ export default {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 192px);
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -223,6 +232,7 @@ export default {
|
|||
img {
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mv">
|
||||
<div class="mv-page">
|
||||
<div class="current-video">
|
||||
<div class="video">
|
||||
<video ref="videoPlayer" class="plyr"></video>
|
||||
|
@ -136,8 +136,9 @@ export default {
|
|||
--plyr-control-radius: 8px;
|
||||
}
|
||||
|
||||
.mv {
|
||||
.mv-page {
|
||||
width: 100%;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.current-video {
|
||||
width: 100%;
|
||||
|
@ -176,6 +177,7 @@ export default {
|
|||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
opacity: 0.88;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-show="show">
|
||||
<div v-show="show" class="playlist">
|
||||
<div
|
||||
v-if="specialPlaylistInfo === undefined && !isLikeSongsPage"
|
||||
class="playlist-info"
|
||||
|
@ -174,6 +174,16 @@
|
|||
"
|
||||
/>
|
||||
|
||||
<div class="load-more">
|
||||
<ButtonTwoTone
|
||||
v-show="hasMore"
|
||||
color="grey"
|
||||
:loading="loadingMore"
|
||||
@click.native="loadMore(100)"
|
||||
>{{ $t('explore.loadMore') }}</ButtonTwoTone
|
||||
>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
:show="showFullDescription"
|
||||
:close="toggleFullDescription"
|
||||
|
@ -346,6 +356,7 @@ export default {
|
|||
showFullDescription: false,
|
||||
tracks: [],
|
||||
loadingMore: false,
|
||||
hasMore: false,
|
||||
lastLoadedTrackIndex: 9,
|
||||
displaySearchInPlaylist: false, // 是否显示搜索框
|
||||
searchKeyWords: '', // 搜索使用的关键字
|
||||
|
@ -397,9 +408,6 @@ export default {
|
|||
this.loadData(this.$route.params.id);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll, true);
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['appendTrackToPlayerList']),
|
||||
...mapActions(['playFirstTrackOnList', 'playTrackOnListByID', 'showToast']),
|
||||
|
@ -443,9 +451,6 @@ export default {
|
|||
if (next !== undefined) next();
|
||||
this.show = true;
|
||||
this.lastLoadedTrackIndex = data.playlist.tracks.length - 1;
|
||||
if (this.playlist.trackCount > this.tracks.length) {
|
||||
window.addEventListener('scroll', this.handleScroll, true);
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -455,36 +460,26 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
loadMore(loadNum = 50) {
|
||||
loadMore(loadNum = 100) {
|
||||
let trackIDs = this.playlist.trackIds.filter((t, index) => {
|
||||
if (
|
||||
index > this.lastLoadedTrackIndex &&
|
||||
index <= this.lastLoadedTrackIndex + loadNum
|
||||
)
|
||||
) {
|
||||
return t;
|
||||
}
|
||||
});
|
||||
trackIDs = trackIDs.map(t => t.id);
|
||||
getTrackDetail(trackIDs.join(',')).then(data => {
|
||||
this.tracks.push(...data.songs);
|
||||
this.lastLoadedTrackIndex += trackIDs.length;
|
||||
this.loadingMore = false;
|
||||
});
|
||||
},
|
||||
handleScroll(e) {
|
||||
let dom = document.querySelector('html');
|
||||
let scrollHeight = Math.max(dom.scrollHeight, dom.scrollHeight);
|
||||
let scrollTop = e.target.scrollingElement.scrollTop;
|
||||
let clientHeight =
|
||||
dom.innerHeight || Math.min(dom.clientHeight, dom.clientHeight);
|
||||
if (clientHeight + scrollTop + 200 >= scrollHeight) {
|
||||
if (
|
||||
this.lastLoadedTrackIndex + 1 === this.playlist.trackIds.length ||
|
||||
this.loadingMore
|
||||
)
|
||||
return;
|
||||
this.loadingMore = true;
|
||||
this.loadMore();
|
||||
if (this.lastLoadedTrackIndex + 1 === this.playlist.trackIds.length) {
|
||||
this.hasMore = false;
|
||||
} else {
|
||||
this.hasMore = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
openMenu(e) {
|
||||
this.$refs.playlistMenu.openMenu(e);
|
||||
|
@ -546,6 +541,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.playlist {
|
||||
margin-top: 32px;
|
||||
}
|
||||
.playlist-info {
|
||||
display: flex;
|
||||
margin-bottom: 72px;
|
||||
|
@ -936,4 +934,10 @@ export default {
|
|||
right: 8vw;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-show="show" class="search">
|
||||
<div v-show="show" class="search-page">
|
||||
<div v-show="artists.length > 0 || albums.length > 0" class="row">
|
||||
<div v-show="artists.length > 0" class="artists">
|
||||
<div v-show="artists.length > 0" class="section-title"
|
||||
|
@ -237,7 +237,7 @@ export default {
|
|||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 98px;
|
||||
margin-top: 32px;
|
||||
|
||||
.artists {
|
||||
flex: 1;
|
||||
|
|
|
@ -139,7 +139,7 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
h1 {
|
||||
margin-top: -10px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 28px;
|
||||
color: var(--color-text);
|
||||
span {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="settings">
|
||||
<div class="settings-page">
|
||||
<div class="container">
|
||||
<div v-if="showUserInfo" class="user">
|
||||
<div class="left">
|
||||
|
@ -765,9 +765,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings {
|
||||
.settings-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.container {
|
||||
margin-top: 24px;
|
||||
|
|
Loading…
Reference in New Issue
Block a user