improve: complete event-driven animation

This commit is contained in:
alikia2x (寒寒) 2024-11-23 06:17:53 +08:00
parent a4ddd83f91
commit 2742ba43f2
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
3 changed files with 69 additions and 59 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "aquavox", "name": "aquavox",
"version": "2.9.1", "version": "2.9.2",
"private": false, "private": false,
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",

View File

@ -3,9 +3,11 @@
import type { LyricWord, ScriptItem } from '@core/lyrics/type'; import type { LyricWord, ScriptItem } from '@core/lyrics/type';
import type { LyricPos } from './type'; import type { LyricPos } from './type';
import type { Spring } from '@core/graphics/spring/spring'; import type { Spring } from '@core/graphics/spring/spring';
import userAdjustingProgress from '@core/state/userAdjustingProgress';
const viewportWidth = document.documentElement.clientWidth; const viewportWidth = document.documentElement.clientWidth;
const blurRatio = viewportWidth > 640 ? 1.2 : 1.4; const blurRatio = viewportWidth > 640 ? 1.2 : 1.4;
const scrollDuration = 0.2;
export let line: ScriptItem; export let line: ScriptItem;
export let index: number; export let index: number;
@ -23,6 +25,10 @@
let positionY: number = 0; let positionY: number = 0;
let blur = 0; let blur = 0;
let stopped = false; let stopped = false;
let we_are_scrolling = false;
let scrollTarget: number | undefined = undefined;
let scrollFrom: number | undefined = undefined;
let scrollingStartTime: number | undefined = undefined;
let lastPosX: number | undefined = undefined; let lastPosX: number | undefined = undefined;
let lastPosY: number | undefined = undefined; let lastPosY: number | undefined = undefined;
let lastUpdateY: number | undefined = undefined; let lastUpdateY: number | undefined = undefined;
@ -39,7 +45,7 @@
time = (new Date().getTime() - lastUpdateY) / 1000; time = (new Date().getTime() - lastUpdateY) / 1000;
springY.update(time); springY.update(time);
positionY = springY.getCurrentPosition(); positionY = springY.getCurrentPosition();
if (!springY.arrived() && !stopped) { if (!springY.arrived() && !stopped && !we_are_scrolling) {
requestAnimationFrame(updateY); requestAnimationFrame(updateY);
} }
lastUpdateY = new Date().getTime(); lastUpdateY = new Date().getTime();
@ -87,6 +93,7 @@
blurRadius = Math.min(offset * blurRatio, 16); blurRadius = Math.min(offset * blurRatio, 16);
} }
if (scrolling) blurRadius = 0; if (scrolling) blurRadius = 0;
if ($userAdjustingProgress) blurRadius = 0;
blur = blurRadius blur = blurRadius
} }
} }
@ -107,6 +114,26 @@
lastPosY = pos.y; lastPosY = pos.y;
}; };
function updateScroll(timestamp: number) {
const elapsedTime = (new Date().getTime() - scrollingStartTime!) / 1000;
const percentage = Math.min(elapsedTime / scrollDuration, 1);
positionY = scrollFrom! + (scrollTarget! - scrollFrom!) * percentage;
if (percentage < 1) {
requestAnimationFrame(updateScroll);
}
}
export const scrollTo = (targetY: number) => {
scrollFrom = positionY;
scrollTarget = targetY;
scrollingStartTime = new Date().getTime();
we_are_scrolling = true;
requestAnimationFrame(updateScroll);
springY!.setPosition(targetY);
we_are_scrolling = false;
};
export const getInfo = () => { export const getInfo = () => {
return { return {
x: positionX, x: positionX,

View File

@ -3,7 +3,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import LyricLine from './lyricLine.svelte'; import LyricLine from './lyricLine.svelte';
import createLyricsSearcher from '@core/lyrics/lyricSearcher'; import createLyricsSearcher from '@core/lyrics/lyricSearcher';
import { createRule } from 'eslint-plugin-svelte/lib/utils'; import userAdjustingProgress from '@core/state/userAdjustingProgress';
// constants // constants
const viewportHeight = document.documentElement.clientHeight; const viewportHeight = document.documentElement.clientHeight;
@ -48,7 +48,8 @@
function initLyricComponents() { function initLyricComponents() {
initLyricTopList(); initLyricTopList();
for (let i = 0; i < lyricComponents.length; i++) { for (let i = 0; i < lyricComponents.length; i++) {
lyricComponents[i].init({ x: 0, y: lyricTopList[i] }); const currentLyric = lyricComponents[i];
currentLyric.init({ x: 0, y: lyricTopList[i] });
} }
} }
@ -85,6 +86,16 @@
} }
} }
function seekForward() {
if (!originalLyrics.scripts) return;
const relativeOrigin = lyricTopList[currentLyricIndex] - currentLyricTop;
for (let i = 0; i < lyricElements.length; i++) {
const currentLyricComponent = lyricComponents[i];
currentLyricComponent.scrollTo(lyricTopList[i] - relativeOrigin);
}
lastSeekForward = new Date().getTime();
}
$effect(() => { $effect(() => {
if (!originalLyrics || !originalLyrics.scripts) return; if (!originalLyrics || !originalLyrics.scripts) return;
lyricLines = originalLyrics.scripts!; lyricLines = originalLyrics.scripts!;
@ -171,69 +182,40 @@
scrollEventAdded = true; scrollEventAdded = true;
}); });
let lastTriggered = $state(0);
let lastEventLyricIndex = $state(0); let lastEventLyricIndex = $state(0);
let lastEventProgress = $state(0); let lastEventProgress = $state(0);
let lastSeekForward = $state(0);
$effect(() => { $effect(() => {
const progressDelta = progress - lastEventProgress; const progressDelta = progress - lastEventProgress;
const deltaInRange = 0 <= progressDelta && progressDelta <= 0.15; const deltaInRange = 0 <= progressDelta && progressDelta <= 0.15;
const deltaTooBig = progressDelta > 0.15; const deltaTooBig = progressDelta > 0.15;
const deltaIsNegative = progressDelta < 0; const deltaIsNegative = progressDelta < 0;
const lyricChanged = currentLyricIndex !== lastEventLyricIndex; const lyricChanged = currentLyricIndex !== lastEventLyricIndex;
const lyricIndexDeltaTooBig = Math.abs(currentLyricIndex - lastEventLyricIndex) > 1; const lyricIndexDeltaTooBig = Math.abs(currentLyricIndex - lastEventLyricIndex) > 1;
if (lyricChanged && !lyricIndexDeltaTooBig && deltaInRange) {
console.log("Event: regular move");
}
else if (deltaTooBig && lyricChanged) {
console.log("Event: seek forward");
}
else if (deltaIsNegative && lyricChanged) {
console.log("Event: seek backward");
}
lastEventLyricIndex = currentLyricIndex;
lastEventProgress = progress;
});
$effect(() => { lastEventLyricIndex = currentLyricIndex;
if (!lyricsContainer || lyricComponents.length < 0) return; lastEventProgress = progress;
if (progress >= nextUpdate - 0.5 && !scrolling) { if (!lyricChanged) return;
if (!lyricIndexDeltaTooBig && deltaInRange) {
console.log("Event: regular move");
console.log(new Date().getTime() , lastSeekForward);
computeLayout(); computeLayout();
} }
if (Math.abs(lastProgress - progress) > 0.5) { else if ($userAdjustingProgress) {
scrolling = false; if (deltaTooBig && lyricChanged) {
} console.log("Event: seek forward");
if (lastProgress - progress > 0) { seekForward();
computeLayout(); } else if (deltaIsNegative && lyricChanged) {
nextUpdate = progress; console.log("Event: seek backward");
} else { seekForward();
const lyricLength = originalLyrics.scripts!.length;
const currentEnd = originalLyrics.scripts![currentLyricIndex].end;
const nextStart = originalLyrics.scripts![Math.min(currentLyricIndex + 1, lyricLength - 1)].start;
if (currentEnd !== nextStart) {
nextUpdate = currentEnd;
} else {
nextUpdate = nextStart;
} }
} }
lastProgress = progress; else {
console.log("Event: regular move");
computeLayout();
}
}); });
// $: {
// for (let i = 0; i < lyricElements.length; i++) {
// const s = originalLyrics.scripts![i].start;
// const t = originalLyrics.scripts![i].end;
// // Explain:
// // The `currentLyricIndex` is also used for locating & layout computing,
// // so when the current progress is in the interlude between two lyrics,
// // `currentLyricIndex` still needs to have a valid value to ensure that
// // the style and scrolling position are calculated correctly.
// // But in that situation, the “current lyric index” does not exist.
// const isCurrent = i == currentLyricIndex && s <= progress && progress <= t;
// const currentLyricComponent = lyricComponents[i];
// currentLyricComponent.setCurrent(isCurrent);
// }
// }
onMount(() => { onMount(() => {
// Initialize // Initialize
if (localStorage.getItem('debugMode') == null) { if (localStorage.getItem('debugMode') == null) {
@ -258,16 +240,17 @@
function lyricClick(lyricIndex: number) { function lyricClick(lyricIndex: number) {
if (player === null || originalLyrics.scripts === undefined) return; if (player === null || originalLyrics.scripts === undefined) return;
player.currentTime = originalLyrics.scripts[lyricIndex].start; player.currentTime = originalLyrics.scripts[lyricIndex].start;
userAdjustingProgress.set(false);
player.play(); player.play();
} }
</script> </script>
<!--<svelte:window on:keydown={onKeyDown} />--> <svelte:window on:keydown={onKeyDown} />
{#if debugMode} {#if debugMode}
<span <span
class="text-white text-lg absolute z-50 px-2 py-0.5 m-2 rounded-3xl bg-white bg-opacity-20 backdrop-blur-lg right-0 font-mono"> class="text-white text-lg absolute z-50 px-2 py-0.5 m-2 rounded-3xl bg-white bg-opacity-20 backdrop-blur-lg right-0 font-mono">
progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex} progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex}, uap: {$userAdjustingProgress}
</span> </span>
{/if} {/if}