mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 02:47:22 +08:00
✨ feat: 优化歌词背景 修改为背景色 以解决卡顿问题
This commit is contained in:
@@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-drawer :show="musicFull" height="100vh" placement="bottom" :style="{ backgroundColor: 'transparent' }">
|
<n-drawer :show="musicFull" height="100vh" placement="bottom" :style="{ backgroundColor: 'transparent' }">
|
||||||
<div id="drawer-target">
|
<div id="drawer-target">
|
||||||
<div
|
<div class="drawer-back" :style="{ background: background }"></div>
|
||||||
class="drawer-back"
|
|
||||||
:class="{ paused: !isPlaying }"
|
|
||||||
:style="{ backgroundImage: `url(${getImgUrl(playMusic?.picUrl, '300y300')})` }"
|
|
||||||
></div>
|
|
||||||
<div class="music-img">
|
<div class="music-img">
|
||||||
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
||||||
</div>
|
</div>
|
||||||
@@ -62,6 +58,13 @@ import { getImgUrl } from '@/utils';
|
|||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
|
// 播放的音乐信息
|
||||||
|
const playMusic = computed(() => store.state.playMusic as SongResult);
|
||||||
|
// const isPlaying = computed(() => store.state.play as boolean);
|
||||||
|
// 获取歌词滚动dom
|
||||||
|
const lrcSider = ref<any>(null);
|
||||||
|
const isMouse = ref(false);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
musicFull: {
|
musicFull: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -71,14 +74,12 @@ const props = defineProps({
|
|||||||
type: HTMLAudioElement,
|
type: HTMLAudioElement,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 播放的音乐信息
|
|
||||||
const playMusic = computed(() => store.state.playMusic as SongResult);
|
|
||||||
const isPlaying = computed(() => store.state.play as boolean);
|
|
||||||
// 获取歌词滚动dom
|
|
||||||
const lrcSider = ref<any>(null);
|
|
||||||
const isMouse = ref(false);
|
|
||||||
// 歌词滚动方法
|
// 歌词滚动方法
|
||||||
const lrcScroll = () => {
|
const lrcScroll = () => {
|
||||||
if (props.musicFull && !isMouse.value) {
|
if (props.musicFull && !isMouse.value) {
|
||||||
@@ -110,14 +111,16 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.drawer-back {
|
.drawer-back {
|
||||||
@apply absolute bg-cover bg-center opacity-70;
|
@apply absolute bg-cover bg-center;
|
||||||
filter: blur(80px) brightness(80%);
|
filter: brightness(80%);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
width: 200%;
|
width: 200%;
|
||||||
height: 200%;
|
height: 200%;
|
||||||
top: -50%;
|
top: -50%;
|
||||||
left: -50%;
|
left: -50%;
|
||||||
animation: round 20s linear infinite;
|
// animation: round 20s linear infinite;
|
||||||
|
// will-change: transform;
|
||||||
|
// transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-back.paused {
|
.drawer-back.paused {
|
||||||
@@ -162,7 +165,7 @@ defineExpose({
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
height: 550px;
|
height: 550px;
|
||||||
.now-text {
|
.now-text {
|
||||||
@apply text-red-500;
|
@apply text-green-500;
|
||||||
}
|
}
|
||||||
&-text {
|
&-text {
|
||||||
@apply text-white text-lg flex flex-col justify-center items-center cursor-pointer font-bold;
|
@apply text-white text-lg flex flex-col justify-center items-center cursor-pointer font-bold;
|
||||||
@@ -170,7 +173,7 @@ defineExpose({
|
|||||||
transition: all 0.2s ease-out;
|
transition: all 0.2s ease-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply font-bold text-red-500;
|
@apply font-bold text-green-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-tr {
|
&-tr {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 展开全屏 -->
|
<!-- 展开全屏 -->
|
||||||
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :audio="audio.value as HTMLAudioElement" />
|
<music-full
|
||||||
|
ref="MusicFullRef"
|
||||||
|
v-model:music-full="musicFullVisible"
|
||||||
|
:audio="audio.value as HTMLAudioElement"
|
||||||
|
:background="background"
|
||||||
|
/>
|
||||||
<!-- 底部播放栏 -->
|
<!-- 底部播放栏 -->
|
||||||
<div
|
<div
|
||||||
class="music-play-bar"
|
class="music-play-bar"
|
||||||
@@ -100,6 +105,7 @@ import SongItem from '@/components/common/SongItem.vue';
|
|||||||
import { allTime, isElectron, loadLrc, nowTime, openLyric, sendLyricToWin } from '@/hooks/MusicHook';
|
import { allTime, isElectron, loadLrc, nowTime, openLyric, sendLyricToWin } from '@/hooks/MusicHook';
|
||||||
import type { SongResult } from '@/type/music';
|
import type { SongResult } from '@/type/music';
|
||||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||||
|
import { getImageLinearBackground } from '@/utils/linearColor';
|
||||||
|
|
||||||
import MusicFull from './MusicFull.vue';
|
import MusicFull from './MusicFull.vue';
|
||||||
|
|
||||||
@@ -115,13 +121,15 @@ const playList = computed(() => store.state.playList as SongResult[]);
|
|||||||
const audio = {
|
const audio = {
|
||||||
value: document.querySelector('#MusicAudio') as HTMLAudioElement,
|
value: document.querySelector('#MusicAudio') as HTMLAudioElement,
|
||||||
};
|
};
|
||||||
|
const background = ref('#000');
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => store.state.playMusicUrl,
|
() => store.state.playMusic,
|
||||||
() => {
|
async () => {
|
||||||
loadLrc(playMusic.value.id);
|
loadLrc(playMusic.value.id);
|
||||||
|
background.value = await getImageLinearBackground(getImgUrl(playMusic.value?.picUrl, '300y300'));
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const audioPlay = () => {
|
const audioPlay = () => {
|
||||||
@@ -273,8 +281,8 @@ const setMusicFull = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-play {
|
&-play {
|
||||||
@apply flex justify-center items-center w-12 h-12 rounded-full mx-4 hover:bg-green-500 transition;
|
|
||||||
background: #383838;
|
background: #383838;
|
||||||
|
@apply flex justify-center items-center w-12 h-12 rounded-full mx-4 hover:bg-green-500 transition bg-opacity-40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -63,14 +63,14 @@ export const getMusicProxyUrl = (url: string) => {
|
|||||||
return `${ProxyUrl}/mc?url=${PUrl}`;
|
return `${ProxyUrl}/mc?url=${PUrl}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getImgUrl = computed(() => (url: string | undefined, size: string = '') => {
|
export const getImgUrl = (url: string | undefined, size: string = '') => {
|
||||||
const bdUrl = 'https://image.baidu.com/search/down?url=';
|
const bdUrl = 'https://image.baidu.com/search/down?url=';
|
||||||
const imgUrl = `${url}?param=${size}`;
|
const imgUrl = `${url}?param=${size}`;
|
||||||
if (!getIsMc()) {
|
if (!getIsMc()) {
|
||||||
return imgUrl;
|
return imgUrl;
|
||||||
}
|
}
|
||||||
return `${bdUrl}${encodeURIComponent(imgUrl)}`;
|
return `${bdUrl}${encodeURIComponent(imgUrl)}`;
|
||||||
});
|
};
|
||||||
|
|
||||||
export const isMobile = computed(() => {
|
export const isMobile = computed(() => {
|
||||||
const flag = navigator.userAgent.match(
|
const flag = navigator.userAgent.match(
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
export const getImageLinearBackground = async (imageSrc: string): Promise<string> => {
|
||||||
|
const primaryColor = await getImagePrimaryColor(imageSrc);
|
||||||
|
return generateGradientBackground(primaryColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImagePrimaryColor = (imageSrc: string): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'Anonymous';
|
||||||
|
img.src = imageSrc;
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('无法获取canvas上下文'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
const color = getAverageColor(imageData.data);
|
||||||
|
resolve(`rgb(${color.join(',')})`);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => reject(new Error('图片加载失败'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAverageColor = (data: Uint8ClampedArray): number[] => {
|
||||||
|
let r = 0;
|
||||||
|
let g = 0;
|
||||||
|
let b = 0;
|
||||||
|
let count = 0;
|
||||||
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
|
r += data[i];
|
||||||
|
g += data[i + 1];
|
||||||
|
b += data[i + 2];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return [Math.round(r / count), Math.round(g / count), Math.round(b / count)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateGradientBackground = (color: string): string => {
|
||||||
|
const [r, g, b] = color.match(/\d+/g)?.map(Number) || [0, 0, 0];
|
||||||
|
const [h, s, l] = rgbToHsl(r, g, b);
|
||||||
|
|
||||||
|
// 增加亮度和暗度的差异
|
||||||
|
const lightL = Math.min(l + 0.8, 0.95);
|
||||||
|
const darkL = Math.max(l - 0.5, 0.05);
|
||||||
|
const midL = (lightL + darkL) / 2;
|
||||||
|
|
||||||
|
// 调整饱和度以增强效果
|
||||||
|
const lightS = Math.min(s * 0.8, 1);
|
||||||
|
const darkS = Math.min(s * 1.2, 1);
|
||||||
|
|
||||||
|
const [lightR, lightG, lightB] = hslToRgb(h, lightS, lightL);
|
||||||
|
const [midR, midG, midB] = hslToRgb(h, s, midL);
|
||||||
|
const [darkR, darkG, darkB] = hslToRgb(h, darkS, darkL);
|
||||||
|
|
||||||
|
const lightColor = `rgb(${lightR}, ${lightG}, ${lightB})`;
|
||||||
|
const midColor = `rgb(${midR}, ${midG}, ${midB})`;
|
||||||
|
const darkColor = `rgb(${darkR}, ${darkG}, ${darkB})`;
|
||||||
|
|
||||||
|
// 使用三个颜色点创建更丰富的渐变
|
||||||
|
return `linear-gradient(to bottom, ${lightColor} 0%, ${midColor} 50%, ${darkColor} 100%)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions (unchanged)
|
||||||
|
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
|
||||||
|
r /= 255;
|
||||||
|
g /= 255;
|
||||||
|
b /= 255;
|
||||||
|
const max = Math.max(r, g, b);
|
||||||
|
const min = Math.min(r, g, b);
|
||||||
|
let h = 0;
|
||||||
|
let s;
|
||||||
|
const l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
h = s = 0;
|
||||||
|
} else {
|
||||||
|
const d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
h = (b - r) / d + 2;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
h = (r - g) / d + 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, l];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
||||||
|
let r;
|
||||||
|
let g;
|
||||||
|
let b;
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l;
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p: number, q: number, t: number) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user