From b111b07c137c2d998c0a6b10d1758b419067d7c7 Mon Sep 17 00:00:00 2001 From: Shomi <96868062+Shomi-FJS@users.noreply.github.com> Date: Thu, 21 May 2026 19:16:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8=E7=BB=84=E4=BB=B6=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=A7=A6=E6=91=B8=E4=BA=8B=E4=BB=B6=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E8=BF=9B=E5=BA=A6=E6=9D=A1=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: - AMLL 歌词组件添加触摸锁定与帧 delta 上限,防止滑动冲突和跳帧 - MainPlayer 重构拖拽开启全屏播放器逻辑: - 将 pointer 事件绑定到底栏本体 (.main-player-body),避免进度条误触 - 新增 initDragOpen 重试机制(最多 8 帧),解决 Transition 异步挂载竞态 - 新增 pendingCloseFinalize 机制,处理关闭动作被打断时的卡死问题 - 添加 onBeforeUnmount 清理所有 timer/RAF/内联样式 - FullPlayerMobile 缓存歌词分页状态,修复切歌后打开空白歌词页的 bug - PlayerSlider 新增 tooltip 延迟隐藏逻辑,避免拖拽后 tooltip 立即消失 - 移除 backdropFilter/contain 内联样式,减少合成层开销 - FullPlayer 添加 electron IPC 可选链,兼容 Android 环境 --- src/components/AMLL/LyricPlayer.vue | 23 +- src/components/List/SongList.vue | 2 +- src/components/Player/FullPlayer.vue | 10 +- src/components/Player/FullPlayerMobile.vue | 19 +- src/components/Player/MainPlayer.vue | 630 +++++++++++------- .../Player/PlayerComponents/PlayerSlider.vue | 29 +- 6 files changed, 440 insertions(+), 273 deletions(-) diff --git a/src/components/AMLL/LyricPlayer.vue b/src/components/AMLL/LyricPlayer.vue index 67ae53650..97ac3ecf9 100644 --- a/src/components/AMLL/LyricPlayer.vue +++ b/src/components/AMLL/LyricPlayer.vue @@ -227,7 +227,7 @@ const syncPlaybackTime = (time: number) => { const player = getInternalPlayer(); if (!player) return; - const previousHotLines = new Set(player.hotLines ?? []); + const previousHotLines: Set = new Set(player.hotLines ?? []); player.setCurrentTime(time, false); syncNewHotLineAnimations(player, previousHotLines, time); }; @@ -262,7 +262,9 @@ onMounted(() => { const wrapper = wrapperRef.value; if (wrapper) { playerRef.value = new CoreLyricPlayer(); - wrapper.appendChild(playerRef.value.getElement()); + const el = playerRef.value.getElement(); + el.style.touchAction = "none"; + wrapper.appendChild(el); playerRef.value.addEventListener("line-click", lineClickHandler); playerRef.value.addEventListener("line-contextmenu", lineContextMenuHandler); } @@ -278,23 +280,36 @@ onUnmounted(() => { } }); -// 动画帧更新 +// 限制单帧最大时间步长,避免后台恢复后动画突跳 +// 设为 120ms 兼顾低端机偶发卡顿(≈30fps 下 33ms/帧),同时防止可见性切换前累计的大 delta 一次注入 +const MAX_FRAME_DELTA = 120; + watchEffect((onCleanup) => { if (!props.disabled) { let canceled = false; let lastTime = -1; + const resetLastTime = () => { + lastTime = -1; + }; + const onVisibility = () => { + resetLastTime(); + }; + document.addEventListener("visibilitychange", onVisibility); const onFrame = (time: number) => { if (canceled) return; if (lastTime === -1) { lastTime = time; } - playerRef.value?.update(time - lastTime); + const rawDelta = time - lastTime; + const delta = rawDelta > MAX_FRAME_DELTA ? MAX_FRAME_DELTA : rawDelta; + playerRef.value?.update(delta); lastTime = time; requestAnimationFrame(onFrame); }; requestAnimationFrame(onFrame); onCleanup(() => { canceled = true; + document.removeEventListener("visibilitychange", onVisibility); }); } }); diff --git a/src/components/List/SongList.vue b/src/components/List/SongList.vue index 451236cc2..f911fc3e9 100644 --- a/src/components/List/SongList.vue +++ b/src/components/List/SongList.vue @@ -122,7 +122,7 @@ ? handlePointerDown($event, index, item.data.name || '未知曲目') : undefined " - @touchstart=" + @touchstart.passive=" draggable ? handlePointerDown($event, index, item.data.name || '未知曲目') : undefined diff --git a/src/components/Player/FullPlayer.vue b/src/components/Player/FullPlayer.vue index 779e60684..a744e018d 100644 --- a/src/components/Player/FullPlayer.vue +++ b/src/components/Player/FullPlayer.vue @@ -134,8 +134,6 @@ const onMobileEnter = (el: Element, done: () => void) => { parent.style.transform = "translate3d(0, 100vh, 0) scale(0.92)"; parent.style.borderRadius = "28px"; parent.style.backfaceVisibility = "hidden"; - parent.style.backdropFilter = "blur(48px)"; - parent.style.contain = "paint"; // 强制重排,确保起始状态生效 parent.getBoundingClientRect(); requestAnimationFrame(() => { @@ -148,8 +146,6 @@ const onMobileEnter = (el: Element, done: () => void) => { parent.style.willChange = ""; parent.style.transformOrigin = ""; parent.style.backfaceVisibility = ""; - parent.style.backdropFilter = ""; - parent.style.contain = ""; done(); }, 380); }; @@ -165,8 +161,6 @@ const onMobileLeave = (el: Element, done: () => void) => { parent.style.transition = MOBILE_CARD_LEAVE; parent.style.borderRadius = "28px"; parent.style.backfaceVisibility = "hidden"; - parent.style.backdropFilter = "blur(48px)"; - parent.style.contain = "paint"; requestAnimationFrame(() => { parent.style.transform = "translate3d(0, 100vh, 0) scale(0.92)"; }); @@ -339,13 +333,13 @@ watch( onMounted(() => { mainCoverColor.value = statusStore.mainColor; if (isElectron && settingStore.preventSleep) { - window.electron.ipcRenderer.send("prevent-sleep", true); + window.electron?.ipcRenderer.send("prevent-sleep", true); } }); onBeforeUnmount(() => { stopShow(); - if (isElectron) window.electron.ipcRenderer.send("prevent-sleep", false); + if (isElectron) window.electron?.ipcRenderer.send("prevent-sleep", false); }); diff --git a/src/components/Player/FullPlayerMobile.vue b/src/components/Player/FullPlayerMobile.vue index 5aaab6878..d0841d09a 100644 --- a/src/components/Player/FullPlayerMobile.vue +++ b/src/components/Player/FullPlayerMobile.vue @@ -1,3 +1,7 @@ + +