8989 'end-with-space': text.word.endsWith(' ') || text.startTime === 0,
9090 }"
9191 >
92- <span class =" word" :style =" { color: lyricConfig.unplayedColor }" >
93- {{ text.word }}
94- </span >
9592 <span
96- class =" filler"
97- :style =" [{ color: lyricConfig.playedColor }, getYrcStyle(text, line.index)]"
93+ class =" word"
94+ :style =" [
95+ {
96+ backgroundImage: `linear-gradient(to right, ${lyricConfig.playedColor} 50%, ${lyricConfig.unplayedColor} 50%)`,
97+ textShadow: 'none',
98+ filter: `drop-shadow(0 0 1px ${lyricConfig.shadowColor}) drop-shadow(0 0 2px ${lyricConfig.shadowColor})`,
99+ },
100+ getYrcStyle(text, line.index),
101+ ]"
98102 >
99103 {{ text.word }}
100104 </span >
121125</template >
122126
123127<script setup lang="ts">
124- import { useRafFn } from " @vueuse/core" ;
128+ import { useRafFn , useTimeoutFn , useThrottleFn } from " @vueuse/core" ;
125129import { LyricLine , LyricWord } from " @applemusic-like-lyrics/lyric" ;
126130import { LyricConfig , LyricData , RenderLine } from " @/types/desktop-lyric" ;
127131import defaultDesktopLyricConfig from " @/assets/data/lyricConfig" ;
@@ -164,24 +168,22 @@ const desktopLyricRef = ref<HTMLElement>();
164168
165169// hover 状态控制
166170const isHovered = ref <boolean >(false );
167- let hoverTimer: ReturnType <typeof setTimeout > | null = null ;
171+
172+ const { start : startHoverTimer } = useTimeoutFn (
173+ () => {
174+ isHovered .value = false ;
175+ },
176+ 1000 ,
177+ { immediate: false },
178+ );
168179
169180/**
170181 * 处理鼠标移动,更新 hover 状态
171182 */
172183const handleMouseMove = () => {
173184 // 设置 hover 状态(锁定和非锁定状态都响应)
174185 isHovered .value = true ;
175- // 清除之前的定时器
176- if (hoverTimer ) {
177- clearTimeout (hoverTimer );
178- hoverTimer = null ;
179- }
180- // 设置新的定时器,延迟后移除 hover 状态
181- hoverTimer = setTimeout (() => {
182- isHovered .value = false ;
183- hoverTimer = null ;
184- }, 1000 );
186+ startHoverTimer ();
185187};
186188
187189/**
@@ -334,7 +336,7 @@ const renderLyricLines = computed<RenderLine[]>(() => {
334336 */
335337const getYrcStyle = (wordData : LyricWord , lyricIndex : number ) => {
336338 const currentLine = lyricData .yrcData ?.[lyricIndex ];
337- if (! currentLine ) return { WebkitMaskPositionX : " 100%" };
339+ if (! currentLine ) return { backgroundPositionX : " 100%" };
338340 const seekSec = playSeekMs .value ;
339341 const startSec = currentLine .startTime || 0 ;
340342 const endSec = currentLine .endTime || 0 ;
@@ -343,14 +345,12 @@ const getYrcStyle = (wordData: LyricWord, lyricIndex: number) => {
343345
344346 if (! isLineActive ) {
345347 const hasPlayed = seekSec >= (wordData .endTime || 0 );
346- return { WebkitMaskPositionX : hasPlayed ? " 0%" : " 100%" };
348+ return { backgroundPositionX : hasPlayed ? " 0%" : " 100%" };
347349 }
348350 const durationSec = Math .max ((wordData .endTime || 0 ) - (wordData .startTime || 0 ), 0.001 );
349351 const progress = Math .max (Math .min ((seekSec - (wordData .startTime || 0 )) / durationSec , 1 ), 0 );
350352 return {
351- transitionDuration: ` 0s, 0s, 0.35s ` ,
352- transitionDelay: ` 0ms ` ,
353- WebkitMaskPositionX: ` ${100 - progress * 100 }% ` ,
353+ backgroundPositionX: ` ${100 - progress * 100 }% ` ,
354354 };
355355};
356356
@@ -409,6 +409,11 @@ const dragState = reactive({
409409 startWinY: 0 ,
410410 winWidth: 0 ,
411411 winHeight: 0 ,
412+ // 缓存屏幕边界
413+ minX: - 99999 ,
414+ minY: - 99999 ,
415+ maxX: 99999 ,
416+ maxY: 99999 ,
412417});
413418
414419/**
@@ -436,6 +441,14 @@ const startDrag = async (event: MouseEvent) => {
436441 const { width, height } = await window .api .store .get (" lyric" );
437442 const safeWidth = Number (width ) > 0 ? Number (width ) : 800 ;
438443 const safeHeight = Number (height ) > 0 ? Number (height ) : 136 ;
444+ // 如果开启了限制边界,在拖拽开始时预先获取一次屏幕范围
445+ if (lyricConfig .limitBounds ) {
446+ const bounds = await window .electron .ipcRenderer .invoke (" get-virtual-screen-bounds" );
447+ dragState .minX = bounds .minX ?? - 99999 ;
448+ dragState .minY = bounds .minY ?? - 99999 ;
449+ dragState .maxX = bounds .maxX ?? 99999 ;
450+ dragState .maxY = bounds .maxY ?? 99999 ;
451+ }
439452 window .electron .ipcRenderer .send (" toggle-fixed-max-size" , {
440453 width: safeWidth ,
441454 height: safeHeight ,
@@ -456,19 +469,20 @@ const startDrag = async (event: MouseEvent) => {
456469 * 桌面歌词拖动移动
457470 * @param event 鼠标事件
458471 */
459- const onDocMouseMove = async (event : MouseEvent ) => {
472+ const onDocMouseMove = useThrottleFn ( (event : MouseEvent ) => {
460473 if (! dragState .isDragging || lyricConfig .isLock ) return ;
461474 const screenX = event ?.screenX ?? 0 ;
462475 const screenY = event ?.screenY ?? 0 ;
463476 let newWinX = Math .round (dragState .startWinX + (screenX - dragState .startX ));
464477 let newWinY = Math .round (dragState .startWinY + (screenY - dragState .startY ));
465- // 是否限制在屏幕边界(支持多屏)
478+ // 是否限制在屏幕边界(支持多屏)- 使用缓存的边界数据同步计算
466479 if (lyricConfig .limitBounds ) {
467- const { minX, minY, maxX, maxY } = await window .electron .ipcRenderer .invoke (
468- " get-virtual-screen-bounds" ,
480+ newWinX = Math .round (
481+ Math .max (dragState .minX , Math .min (dragState .maxX - dragState .winWidth , newWinX )),
482+ );
483+ newWinY = Math .round (
484+ Math .max (dragState .minY , Math .min (dragState .maxY - dragState .winHeight , newWinY )),
469485 );
470- newWinX = Math .round (Math .max (minX as number , Math .min (maxX - dragState .winWidth , newWinX )));
471- newWinY = Math .round (Math .max (minY as number , Math .min (maxY - dragState .winHeight , newWinY )));
472486 }
473487 window .electron .ipcRenderer .send (
474488 " move-window" ,
@@ -477,7 +491,7 @@ const onDocMouseMove = async (event: MouseEvent) => {
477491 dragState .winWidth ,
478492 dragState .winHeight ,
479493 );
480- };
494+ }, 16 ) ;
481495
482496/**
483497 * 桌面歌词拖动结束
@@ -650,11 +664,6 @@ onBeforeUnmount(() => {
650664 // 解绑事件
651665 document .removeEventListener (" mousedown" , onDocMouseDown );
652666 document .removeEventListener (" mousemove" , handleMouseMove );
653- // 清理定时器
654- if (hoverTimer ) {
655- clearTimeout (hoverTimer );
656- hoverTimer = null ;
657- }
658667 if (dragState .isDragging ) onDocMouseUp ();
659668});
660669 </script >
@@ -739,6 +748,7 @@ onBeforeUnmount(() => {
739748 .lyric-line {
740749 width : 100% ;
741750 line-height : normal ;
751+ padding : 4px 0 ;
742752 overflow : hidden ;
743753 text-overflow : ellipsis ;
744754 white-space : nowrap ;
@@ -761,34 +771,14 @@ onBeforeUnmount(() => {
761771 position : relative ;
762772 display : inline-block ;
763773 .word {
764- opacity : 1 ;
765774 display : inline-block ;
766- }
767- .filler {
768- opacity : 0 ;
769- position : absolute ;
770- left : 0 ;
771- top : 0 ;
772- will-change : -webkit-mask-position-x , transform , opacity ;
773- mask-image : linear-gradient (
774- to right ,
775- rgb (0 , 0 , 0 ) 45.4545454545% ,
776- rgba (0 , 0 , 0 , 0 ) 54.5454545455%
777- );
778- mask-size : 220% 100% ;
779- mask-repeat : no-repeat ;
780- -webkit-mask-image : linear-gradient (
781- to right ,
782- rgb (0 , 0 , 0 ) 45.4545454545% ,
783- rgba (0 , 0 , 0 , 0 ) 54.5454545455%
784- );
785- -webkit-mask-size : 220% 100% ;
786- -webkit-mask-repeat : no-repeat ;
787- transition :
788- opacity 0.3s ,
789- filter 0.3s ,
790- margin 0.3s ,
791- padding 0.3s !important ;
775+ background-clip : text ;
776+ -webkit-background-clip : text ;
777+ color : transparent ;
778+ background-size : 200% 100% ;
779+ background-repeat : no-repeat ;
780+ background-position-x : 100% ;
781+ will-change : background-position-x ;
792782 }
793783 & .end-with-space {
794784 margin-right : 5vh ;
@@ -797,16 +787,6 @@ onBeforeUnmount(() => {
797787 }
798788 }
799789 }
800- & .active {
801- .content-text {
802- .filler {
803- opacity : 1 ;
804- -webkit-mask-position-x : 0% ;
805- transition-property : -webkit-mask-position-x , transform , opacity ;
806- transition-timing-function : linear , ease , ease ;
807- }
808- }
809- }
810790 }
811791 }
812792 & .center {
0 commit comments