Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .nx/version-plans/version-plan-1786893788472.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
core-bundle: patch
Comment thread
apoint123 marked this conversation as resolved.
---

refactor(core): 从基类分离 dom 实现的底栏和间奏点
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
export * from "./bg-render/index.ts";
export type * from "./interfaces.ts";
export * from "./lyric-player/index.ts";
export type * as spring from "./utils/spring.ts";
export * as spring from "./utils/spring.ts";
168 changes: 44 additions & 124 deletions packages/core/src/lyric-player/base/bottom-line.ts
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;
}
36 changes: 31 additions & 5 deletions packages/core/src/lyric-player/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import styles from "#styles/lyric-player.module.css";
import { clampPositive } from "#utils/clamp.ts";
import { areOptimizeOptionsEqual } from "#utils/optimize-lyric.ts";
import type { SpringParams } from "#utils/spring.ts";
import { InterludeDots } from "../dom/interlude-dots.ts";
import { BottomLineEl } from "./bottom-line.ts";
import type { BottomLine } from "./bottom-line.ts";
import {
LayoutAlignAnchor,
LayoutReason,
LayoutReasonStrategyMap,
type MaskObsceneWordsMode,
} from "./consts.ts";
import type { LyricLineGroupBase } from "./group.ts";
import type { InterludeDots } from "./interlude-dots.ts";
import {
type FocalTarget,
LayoutCalculator,
Expand All @@ -32,6 +32,8 @@ import { type ScrollInputType, ScrollInteractionEngine } from "./scroll.ts";
import { getPosYSpringPolicy } from "./spring";
import { TimelineController } from "./timeline.ts";

export type { BottomLine, BottomLineTransforms } from "./bottom-line.ts";
export type { InterludeDots } from "./interlude-dots.ts";
export type { LyricLineBase } from "./line.ts";
export type { LyricDataConfig } from "./lyric-data-manager.ts";

Expand Down Expand Up @@ -120,8 +122,8 @@ export abstract class LyricPlayerBase
bottomLineHeight: 0,
interlude: undefined,
};
protected interludeDots: InterludeDots = new InterludeDots();
protected bottomLine: BottomLineEl = new BottomLineEl(this);
protected interludeDots: InterludeDots;
protected bottomLine: BottomLine;
protected enableBlur = true;
protected enableScale = true;
protected hidePassedLines = false;
Expand Down Expand Up @@ -260,6 +262,9 @@ export abstract class LyricPlayerBase
if (element) this.element = element;
this.element.classList.add("amll-lyric-player");

this.interludeDots = this.createInterludeDots();
this.bottomLine = this.createBottomLine();

this.resizeObserver.observe(this.element);
this.resizeObserver.observe(this.interludeDots.getElement());
this.resizeObserver.observe(this.bottomLine.getElement());
Expand Down Expand Up @@ -599,6 +604,26 @@ export abstract class LyricPlayerBase
*/
protected abstract buildLyricGroups(): void;

/**
* 由子类实现的间奏点组件创建逻辑
*
* 在基类构造函数中调用,子类需要返回对应渲染实现的实例
*
* @remarks 工厂执行时子类字段尚未初始化,实现内不得读取子类实例状态,
* 所需资源应由返回的组件自行创建或延迟获取
*/
protected abstract createInterludeDots(): InterludeDots;

/**
* 由子类实现的底栏组件创建逻辑
*
* 在基类构造函数中调用,子类需要返回对应渲染实现的实例
*
* @remarks 工厂执行时子类字段尚未初始化,实现内不得读取子类实例状态,
* 所需资源应由返回的组件自行创建或延迟获取
*/
protected abstract createBottomLine(): BottomLine;

/**
* 重新构建歌词行和时间状态
*
Expand Down Expand Up @@ -912,7 +937,6 @@ export abstract class LyricPlayerBase
}

this.bottomLine.setTransform(
0,
result.bottomLineY,
finalBottomBlur,
strategy.snapPosY,
Expand Down Expand Up @@ -1070,6 +1094,8 @@ export abstract class LyricPlayerBase
this.scrollEngine.dispose();
this.element.remove();
this.bottomLineObserver.disconnect();
this.interludeDots.dispose();
this.bottomLine.dispose();
window.removeEventListener("pageshow", this.onPageShow);
window.removeEventListener("pagehide", this.onPageHide);
}
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/lyric-player/base/interlude-dots.ts
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;
}
Loading