-
Notifications
You must be signed in to change notification settings - Fork 204
refactor(core): 从基类分离 dom 实现的底栏和间奏点 #588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
apoint123
merged 1 commit into
main
from
refactor/split-dom-interlude-and-bottom-line-from-base
Aug 16, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| core-bundle: patch | ||
| --- | ||
|
|
||
| refactor(core): 从基类分离 dom 实现的底栏和间奏点 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,131 +1,51 @@ | ||
| import type { Disposable, HasElement } from "#interfaces"; | ||
| import styles from "#styles/lyric-player.module.css"; | ||
| import { measure } from "#utils/schedule.ts"; | ||
| import { Spring } from "#utils/spring.ts"; | ||
| import type { LyricPlayerBase } from "."; | ||
| import type { Spring } from "#utils/spring.ts"; | ||
|
|
||
| interface LineTransforms { | ||
| posX: Spring; | ||
| /** 底栏的位移动画弹簧 */ | ||
| export interface BottomLineTransforms { | ||
| posY: Spring; | ||
| } | ||
|
|
||
| export class BottomLineEl implements HasElement, Disposable { | ||
| private element: HTMLElement = document.createElement("div"); | ||
| private left = 0; | ||
| private top = 0; | ||
| // 由 LyricPlayer 来设置 | ||
| lineSize: [number, number] = [0, 0]; | ||
| readonly lineTransforms: LineTransforms = { | ||
| posX: new Spring(0), | ||
| posY: new Spring(0), | ||
| }; | ||
| private isFocused = false; | ||
| private blur = 0; | ||
|
|
||
| private lastTransformStyle = ""; | ||
| private lastFilterStyle = ""; | ||
|
|
||
| constructor(private lyricPlayer: LyricPlayerBase) { | ||
| this.element.setAttribute( | ||
| "class", | ||
| `${styles.lyricLine} ${styles.bottomLine}`, | ||
| ); | ||
| this.element.dataset.bottomLine = "true"; | ||
| this.rebuildStyle(); | ||
| } | ||
| async measureSize(): Promise<[number, number]> { | ||
| const size: [number, number] = await measure(() => [ | ||
| this.element.clientWidth, | ||
| this.element.clientHeight, | ||
| ]); | ||
| return size; | ||
| } | ||
| show(): void { | ||
| this.rebuildStyle(); | ||
| } | ||
| hide(): void { | ||
| this.rebuildStyle(); | ||
| } | ||
| setFocused(focused: boolean): void { | ||
| if (this.isFocused !== focused) { | ||
| this.isFocused = focused; | ||
| if (focused) { | ||
| this.element.dataset.focused = "true"; | ||
| } else { | ||
| delete this.element.dataset.focused; | ||
| } | ||
| } | ||
| } | ||
| private rebuildStyle() { | ||
| const style = this.element.style; | ||
|
|
||
| const posX = this.lineTransforms.posX.getCurrentPosition().toFixed(2); | ||
| const posY = this.lineTransforms.posY.getCurrentPosition().toFixed(2); | ||
| const transformStr = `translate(${posX}px, ${posY}px)`; | ||
|
|
||
| if (this.lastTransformStyle !== transformStr) { | ||
| this.lastTransformStyle = transformStr; | ||
| style.transform = transformStr; | ||
| } | ||
|
|
||
| const blurVal = Math.min(5, this.blur); | ||
| const filterStr = blurVal > 0.01 ? `blur(${blurVal.toFixed(2)}px)` : "none"; | ||
| if (this.lastFilterStyle !== filterStr) { | ||
| this.lastFilterStyle = filterStr; | ||
| style.filter = filterStr; | ||
| } | ||
| } | ||
|
|
||
| getElement(): HTMLElement { | ||
| return this.element; | ||
| } | ||
| /** | ||
| * 底栏组件的抽象接口 | ||
| */ | ||
| export interface BottomLine extends HasElement, Disposable { | ||
| /** | ||
| * 底栏当前测量得到的尺寸 | ||
| * | ||
| * 由播放器的 ResizeObserver 回调写入 | ||
| */ | ||
| lineSize: [number, number]; | ||
|
|
||
| /** | ||
| * 底栏的位移弹簧,目标位置与参数由播放器驱动 | ||
| */ | ||
| readonly lineTransforms: BottomLineTransforms; | ||
|
|
||
| /** | ||
| * 设置底栏是否处于聚焦状态 | ||
| * | ||
| * 一般在歌曲播放完毕且底栏有内容时聚焦到底栏并设为 true | ||
| */ | ||
| setFocused(focused: boolean): void; | ||
|
|
||
| /** | ||
| * 设置底栏的目标位置与模糊值 | ||
| * @param top 底栏的 Y 坐标 | ||
| * @param blur 底栏的模糊度 | ||
| * @param immediate 为 true 时绕过弹簧立刻跳转至目标位置 | ||
| * @param delay 弹簧过渡的延迟,单位为秒 | ||
| */ | ||
| setTransform( | ||
| left: number = this.left, | ||
| top: number = this.top, | ||
| blur = 0, | ||
| immediate = false, | ||
| delay = 0, | ||
| ): void { | ||
| this.left = left; | ||
| this.top = top; | ||
|
|
||
| if (immediate || !this.lyricPlayer.getEnableSpring()) { | ||
| this.blur = Math.min(32, blur); | ||
| if (immediate) this.element.classList.add(styles.tmpDisableTransition); | ||
| this.lineTransforms.posX.setPosition(left); | ||
| this.lineTransforms.posY.setPosition(top); | ||
| if (!this.lyricPlayer.getEnableSpring()) this.show(); | ||
| else this.rebuildStyle(); | ||
| if (immediate) | ||
| requestAnimationFrame(() => { | ||
| this.element.classList.remove(styles.tmpDisableTransition); | ||
| }); | ||
| } else { | ||
| this.blur = Math.min(5, blur); | ||
| this.lineTransforms.posX.setTargetPosition(left, delay); | ||
| this.lineTransforms.posY.setTargetPosition(top, delay); | ||
| } | ||
| } | ||
| update(delta = 0): void { | ||
| if (!this.lyricPlayer.getEnableSpring()) return; | ||
| this.lineTransforms.posX.update(delta); | ||
| this.lineTransforms.posY.update(delta); | ||
| if (this.isInSight) { | ||
| this.show(); | ||
| } else { | ||
| this.hide(); | ||
| } | ||
| } | ||
| get isInSight(): boolean { | ||
| const l = this.lineTransforms.posX.getCurrentPosition(); | ||
| const t = this.lineTransforms.posY.getCurrentPosition(); | ||
| const r = l + this.lineSize[0]; | ||
| const b = t + this.lineSize[1]; | ||
| const pr = this.lyricPlayer.size[0]; | ||
| const pb = this.lyricPlayer.size[1]; | ||
| return !(l > pr || t > pb || r < 0 || b < 0); | ||
| } | ||
| dispose(): void { | ||
| this.element.remove(); | ||
| } | ||
| top?: number, | ||
| blur?: number, | ||
| immediate?: boolean, | ||
| delay?: number, | ||
| ): void; | ||
|
|
||
| /** | ||
| * 逐帧推进弹簧动画并应用样式 | ||
| * @param delta 距离上一次调用的时长,单位为秒 | ||
| */ | ||
| update(delta?: number): void; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import type { Disposable, HasElement } from "#interfaces"; | ||
|
|
||
| /** | ||
| * 间奏点组件的抽象接口 | ||
| */ | ||
| export interface InterludeDots extends HasElement, Disposable { | ||
| /** | ||
| * 设置间奏点元素的变换位置 | ||
| */ | ||
| setTransform(left?: number, top?: number): void; | ||
|
|
||
| /** | ||
| * 设置间奏点动画区间并重新锚定时间 | ||
| * @param interlude 间奏起止时间 | ||
| * @param currentTime 当前播放时间 | ||
| * @param forceReset 是否强制重置动画起点,如 Seek、重新布局或切换间奏时 | ||
| */ | ||
| setInterlude( | ||
| interlude?: [number, number], | ||
| currentTime?: number, | ||
| forceReset?: boolean, | ||
| ): void; | ||
|
|
||
| /** | ||
| * 暂停间奏点动画 | ||
| */ | ||
| pause(): void; | ||
|
|
||
| /** | ||
| * 恢复间奏点动画 | ||
| */ | ||
| resume(): void; | ||
|
|
||
| /** | ||
| * 逐帧推进间奏点动画并写入样式 | ||
| * @param delta 距离上一次调用的时长,单位为毫秒 | ||
| */ | ||
| update(delta?: number): void; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.