mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-23 10:56:28 +02:00
Add support for OpenSubsonic enhanced lyrics (#2208)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
const MIN_SCROLL_DURATION_MS = 350;
|
||||
const MAX_SCROLL_DURATION_MS = 850;
|
||||
const SCROLL_DURATION_PER_PX_MS = 0.55;
|
||||
const INSTANT_SCROLL_THRESHOLD_PX = 2;
|
||||
|
||||
const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;
|
||||
|
||||
export const computeScrollDurationMs = (deltaPx: number): number =>
|
||||
Math.min(
|
||||
Math.max(Math.abs(deltaPx) * SCROLL_DURATION_PER_PX_MS, MIN_SCROLL_DURATION_MS),
|
||||
MAX_SCROLL_DURATION_MS,
|
||||
);
|
||||
|
||||
const activeAnimations = new WeakMap<HTMLElement, number>();
|
||||
|
||||
export const cancelScrollAnimation = (element: HTMLElement): void => {
|
||||
const frameId = activeAnimations.get(element);
|
||||
if (frameId !== undefined) {
|
||||
cancelAnimationFrame(frameId);
|
||||
activeAnimations.delete(element);
|
||||
}
|
||||
};
|
||||
|
||||
export interface AnimateScrollTopOptions {
|
||||
onComplete?: () => void;
|
||||
smooth?: boolean;
|
||||
}
|
||||
|
||||
export const animateScrollTop = (
|
||||
element: HTMLElement,
|
||||
targetTop: number,
|
||||
options: AnimateScrollTopOptions = {},
|
||||
): (() => void) => {
|
||||
const { onComplete, smooth = true } = options;
|
||||
const startTop = element.scrollTop;
|
||||
const delta = targetTop - startTop;
|
||||
|
||||
cancelScrollAnimation(element);
|
||||
|
||||
if (!smooth || Math.abs(delta) <= INSTANT_SCROLL_THRESHOLD_PX) {
|
||||
element.scrollTop = targetTop;
|
||||
onComplete?.();
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const durationMs = computeScrollDurationMs(delta);
|
||||
const startTime = performance.now();
|
||||
let frameId: null | number = null;
|
||||
let cancelled = false;
|
||||
|
||||
const cancel = (): void => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelled = true;
|
||||
if (frameId !== null) {
|
||||
cancelAnimationFrame(frameId);
|
||||
activeAnimations.delete(element);
|
||||
frameId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const step = (now: number): void => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsed = now - startTime;
|
||||
const progress = Math.min(elapsed / durationMs, 1);
|
||||
const eased = easeOutCubic(progress);
|
||||
element.scrollTop = startTop + delta * eased;
|
||||
|
||||
if (progress < 1) {
|
||||
frameId = requestAnimationFrame(step);
|
||||
activeAnimations.set(element, frameId);
|
||||
} else {
|
||||
activeAnimations.delete(element);
|
||||
frameId = null;
|
||||
onComplete?.();
|
||||
}
|
||||
};
|
||||
|
||||
frameId = requestAnimationFrame(step);
|
||||
activeAnimations.set(element, frameId);
|
||||
|
||||
return cancel;
|
||||
};
|
||||
Reference in New Issue
Block a user