mirror of
https://github.com/immich-app/immich.git
synced 2025-01-13 15:35:15 +02:00
use svelte motion tweening for animation (#2788)
It look like Svelte has a concept of 'tweening' for writing animations, which should reduce the complexity of the animation code. Thanks to @probablykasper for finding this. A lot of the logic has been rewritten for reactivity, which further reduces complexity.
This commit is contained in:
parent
a1b9a1d244
commit
329b52e670
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { memoryStore } from '$lib/stores/memory.store';
|
import { memoryStore } from '$lib/stores/memory.store';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@ -19,36 +18,58 @@
|
|||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
import { tweened } from 'svelte/motion';
|
||||||
|
|
||||||
let memoryIndex: number;
|
const parseIndex = (s: string | null, max: number | null) =>
|
||||||
$: {
|
Math.max(Math.min(parseInt(s ?? '') || 0, max ?? 0), 0);
|
||||||
const index = parseInt($page.url.searchParams.get('memory') ?? '') || 0;
|
|
||||||
memoryIndex = index < $memoryStore?.length ? index : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: previousMemory = $memoryStore?.[memoryIndex - 1] || null;
|
$: memoryIndex = parseIndex($page.url.searchParams.get('memory'), $memoryStore?.length - 1);
|
||||||
$: currentMemory = $memoryStore?.[memoryIndex] || null;
|
$: assetIndex = parseIndex($page.url.searchParams.get('asset'), currentMemory?.assets.length - 1);
|
||||||
$: nextMemory = $memoryStore?.[memoryIndex + 1] || null;
|
|
||||||
|
|
||||||
let assetIndex: number;
|
$: previousMemory = $memoryStore?.[memoryIndex - 1];
|
||||||
$: {
|
$: currentMemory = $memoryStore?.[memoryIndex];
|
||||||
const index = parseInt($page.url.searchParams.get('asset') ?? '') || 0;
|
$: nextMemory = $memoryStore?.[memoryIndex + 1];
|
||||||
assetIndex = index < currentMemory?.assets.length ? index : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: previousAsset = currentMemory?.assets[assetIndex - 1] || null;
|
$: previousAsset = currentMemory?.assets[assetIndex - 1];
|
||||||
$: currentAsset = currentMemory?.assets[assetIndex] || null;
|
$: currentAsset = currentMemory?.assets[assetIndex];
|
||||||
$: nextAsset = currentMemory?.assets[assetIndex + 1] || null;
|
$: nextAsset = currentMemory?.assets[assetIndex + 1];
|
||||||
|
|
||||||
$: canAdvance = !!(nextMemory || nextAsset);
|
$: canAdvance = !!(nextMemory || nextAsset);
|
||||||
|
|
||||||
$: if (!canAdvance && browser) {
|
const toNextMemory = () => goto(`?memory=${memoryIndex + 1}`);
|
||||||
pause();
|
const toPreviousMemory = () => goto(`?memory=${memoryIndex - 1}`);
|
||||||
}
|
|
||||||
|
|
||||||
let memoryGallery: HTMLElement;
|
const toNextAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex + 1}`);
|
||||||
let memoryWrapper: HTMLElement;
|
const toPreviousAsset = () => goto(`?memory=${memoryIndex}&asset=${assetIndex - 1}`);
|
||||||
let galleryInView = false;
|
|
||||||
|
const toNext = () => (nextAsset ? toNextAsset() : toNextMemory());
|
||||||
|
const toPrevious = () => (previousAsset ? toPreviousAsset() : toPreviousMemory());
|
||||||
|
|
||||||
|
const progress = tweened<number>(0, {
|
||||||
|
duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
const play = () => progress.set(1);
|
||||||
|
const pause = () => progress.set($progress);
|
||||||
|
|
||||||
|
let paused = false;
|
||||||
|
|
||||||
|
// Play or pause progress when the paused state changes.
|
||||||
|
$: paused ? pause() : play();
|
||||||
|
|
||||||
|
// Progress should be paused when it's no longer possible to advance.
|
||||||
|
$: paused ||= !canAdvance;
|
||||||
|
|
||||||
|
// Advance to the next asset or memory when progress is complete.
|
||||||
|
$: $progress === 1 && toNext();
|
||||||
|
|
||||||
|
// Progress should be resumed when reset and not paused.
|
||||||
|
$: !$progress && !paused && play();
|
||||||
|
|
||||||
|
// Progress should be reset when the current memory or asset changes.
|
||||||
|
$: memoryIndex, assetIndex, progress.set(0);
|
||||||
|
|
||||||
|
onDestroy(() => pause());
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!$memoryStore) {
|
if (!$memoryStore) {
|
||||||
@ -59,67 +80,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => browser && pause());
|
let memoryGallery: HTMLElement;
|
||||||
|
let memoryWrapper: HTMLElement;
|
||||||
const toPreviousMemory = () => previousMemory && goto(`?memory=${memoryIndex - 1}`);
|
let galleryInView = false;
|
||||||
|
|
||||||
const toNextMemory = () => nextMemory && goto(`?memory=${memoryIndex + 1}`);
|
|
||||||
|
|
||||||
const toPreviousAsset = () =>
|
|
||||||
previousAsset ? goto(`?memory=${memoryIndex}&asset=${assetIndex - 1}`) : toPreviousMemory();
|
|
||||||
|
|
||||||
const toNextAsset = () =>
|
|
||||||
nextAsset ? goto(`?memory=${memoryIndex}&asset=${assetIndex + 1}`) : toNextMemory();
|
|
||||||
|
|
||||||
const duration = 5000; // 5 seconds
|
|
||||||
|
|
||||||
let paused = true;
|
|
||||||
let progress = 0;
|
|
||||||
let animationFrameRequest: number;
|
|
||||||
let start: number | null = null;
|
|
||||||
|
|
||||||
const requestDraw = () => (animationFrameRequest = requestAnimationFrame(draw));
|
|
||||||
|
|
||||||
const draw = (now: number) => {
|
|
||||||
requestDraw();
|
|
||||||
|
|
||||||
start ??= now - progress * duration;
|
|
||||||
|
|
||||||
const elapsed = now - start;
|
|
||||||
progress = Math.min(1, elapsed / duration);
|
|
||||||
|
|
||||||
if (progress !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toNextAsset();
|
|
||||||
start = now;
|
|
||||||
};
|
|
||||||
|
|
||||||
const play = () => {
|
|
||||||
if (!canAdvance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
paused = false;
|
|
||||||
requestDraw();
|
|
||||||
};
|
|
||||||
|
|
||||||
const pause = () => {
|
|
||||||
paused = true;
|
|
||||||
cancelAnimationFrame(animationFrameRequest);
|
|
||||||
resetStart();
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetProgress = () => {
|
|
||||||
progress = 0;
|
|
||||||
resetStart();
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetStart = () => (start = null);
|
|
||||||
|
|
||||||
// Progress should be reset when the current memory or asset changes.
|
|
||||||
$: memoryIndex, assetIndex, resetProgress();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
|
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
|
||||||
@ -136,12 +99,12 @@
|
|||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
logo={paused ? Play : Pause}
|
logo={paused ? Play : Pause}
|
||||||
forceDark
|
forceDark
|
||||||
on:click={paused ? play : pause}
|
on:click={() => (paused = !paused)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<span class="absolute left-0 w-full h-[2px] bg-gray-500" />
|
<span class="absolute left-0 w-full h-[2px] bg-gray-500" />
|
||||||
<span class="absolute left-0 h-[2px] bg-white" style:width={`${progress * 100}%`} />
|
<span class="absolute left-0 h-[2px] bg-white" style:width={`${$progress * 100}%`} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -215,7 +178,7 @@
|
|||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
logo={ChevronLeft}
|
logo={ChevronLeft}
|
||||||
backgroundColor="#202123"
|
backgroundColor="#202123"
|
||||||
on:click={toPreviousAsset}
|
on:click={toPrevious}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@ -226,7 +189,7 @@
|
|||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
logo={ChevronRight}
|
logo={ChevronRight}
|
||||||
backgroundColor="#202123"
|
backgroundColor="#202123"
|
||||||
on:click={toNextAsset}
|
on:click={toNext}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user