mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
2aa88d26d4
After PR #214 replaced SpringScroller with EaseScroller, the filename no longer matched the exported class. Rename for clarity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
/**
|
|
* Duration-based ease-out scroll animator.
|
|
*
|
|
* Animates scrollTop from the current position to the target over a fixed
|
|
* duration using a cubic ease-out curve. Calling scrollTo() mid-flight
|
|
* restarts cleanly from wherever the container currently sits, so fast
|
|
* line changes never look jerky or skip.
|
|
*/
|
|
export class EaseScroller {
|
|
private container : HTMLElement;
|
|
private startY = 0;
|
|
private targetY = 0;
|
|
private startTime = 0;
|
|
private rafId: number | null = null;
|
|
|
|
private readonly duration: number;
|
|
|
|
constructor(container: HTMLElement, duration = 650) {
|
|
this.container = container;
|
|
this.targetY = container.scrollTop;
|
|
this.duration = duration;
|
|
}
|
|
|
|
scrollTo(y: number) {
|
|
this.startY = this.container.scrollTop;
|
|
this.targetY = Math.max(0, y);
|
|
this.startTime = performance.now();
|
|
if (this.rafId === null) this.rafId = requestAnimationFrame(this.tick);
|
|
}
|
|
|
|
stop() {
|
|
if (this.rafId !== null) {
|
|
cancelAnimationFrame(this.rafId);
|
|
this.rafId = null;
|
|
}
|
|
}
|
|
|
|
jump(y: number) {
|
|
this.stop();
|
|
this.container.scrollTop = y;
|
|
this.targetY = y;
|
|
}
|
|
|
|
private tick = (now: number) => {
|
|
const t = Math.min((now - this.startTime) / this.duration, 1);
|
|
const ease = 1 - Math.pow(1 - t, 3); // cubic ease-out
|
|
this.container.scrollTop = this.startY + (this.targetY - this.startY) * ease;
|
|
if (t < 1) {
|
|
this.rafId = requestAnimationFrame(this.tick);
|
|
} else {
|
|
this.container.scrollTop = this.targetY;
|
|
this.rafId = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Compute the scroll position that places `el` at `fraction` from the top
|
|
* of `container` (0 = top edge, 0.35 = Apple Music-style, 0.5 = centre).
|
|
*/
|
|
export function targetForFraction(
|
|
container: HTMLElement,
|
|
el : HTMLElement,
|
|
fraction = 0.35,
|
|
): number {
|
|
const cRect = container.getBoundingClientRect();
|
|
const eRect = el.getBoundingClientRect();
|
|
return container.scrollTop + (eRect.top - cRect.top) - cRect.height * fraction + eRect.height / 2;
|
|
}
|