feat: prettier animations

This commit is contained in:
not-nullptr 2024-11-12 01:44:55 +00:00
parent db5d5414db
commit cd91b30119
4 changed files with 183 additions and 53 deletions

View File

@ -1,4 +1,6 @@
import type { EasingFunction, TransitionConfig } from "svelte/transition"; import type { EasingFunction, TransitionConfig } from "svelte/transition";
import type { AnimationConfig, FlipParams } from "svelte/animate";
import { cubicOut } from "svelte/easing";
export const transition = export const transition =
"linear(0,0.006,0.025 2.8%,0.101 6.1%,0.539 18.9%,0.721 25.3%,0.849 31.5%,0.937 38.1%,0.968 41.8%,0.991 45.7%,1.006 50.1%,1.015 55%,1.017 63.9%,1.001)"; "linear(0,0.006,0.025 2.8%,0.101 6.1%,0.539 18.9%,0.721 25.3%,0.849 31.5%,0.937 38.1%,0.968 41.8%,0.991 45.7%,1.006 50.1%,1.015 55%,1.017 63.9%,1.001)";
@ -58,13 +60,15 @@ export const blur = (
"(prefers-reduced-motion: reduce)", "(prefers-reduced-motion: reduce)",
).matches; ).matches;
if (typeof config?.opacity === "undefined" && config) config.opacity = true; if (typeof config?.opacity === "undefined" && config) config.opacity = true;
const isUsingTranslate = !!config?.x || !!config?.y || !!config?.scale;
console.log(isUsingTranslate);
return { return {
delay: config?.delay || 0, delay: config?.delay || 0,
duration: prefersReducedMotion ? 0 : config?.duration || 300, duration: prefersReducedMotion ? 0 : config?.duration || 300,
css: (t) => css: (t) => {
prefersReducedMotion if (prefersReducedMotion) return "";
? "" const translate = isUsingTranslate
: `filter: blur(${(1 - t) * (config?.blurMultiplier || 1)}px); opacity: ${config?.opacity ? t : 1}; transform: translate(${remap( ? `translate(${remap(
t, t,
0, 0,
1, 1,
@ -112,7 +116,57 @@ export const blur = (
config?.scale?.end, config?.scale?.end,
config?.scale?.start, config?.scale?.start,
), ),
)});`, )})`
: ``;
return `filter: blur(${(1 - t) * (config?.blurMultiplier || 1)}px); opacity: ${config?.opacity ? t : 1}; transform: ${
translate
};`;
},
easing: config?.easing, easing: config?.easing,
}; };
}; };
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function is_function(thing: unknown): thing is Function {
return typeof thing === "function";
}
type Params = FlipParams & {};
/**
* The flip function calculates the start and end position of an element and animates between them, translating the x and y values.
* `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/).
*
* https://svelte.dev/docs/svelte-animate#flip
*/
export function flip(
node: HTMLElement,
{ from, to }: { from: DOMRect; to: DOMRect },
params: Params = {},
): AnimationConfig {
const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform;
const [ox, oy] = style.transformOrigin.split(" ").map(parseFloat);
const dx = from.left + (from.width * ox) / to.width - (to.left + ox);
const dy = from.top + (from.height * oy) / to.height - (to.top + oy);
const {
delay = 0,
duration = (d) => Math.sqrt(d) * 120,
easing = cubicOut,
} = params;
return {
delay,
duration: is_function(duration)
? duration(Math.sqrt(dx * dx + dy * dy))
: duration,
easing,
css: (t, u) => {
const x = u * dx;
const y = u * dy;
// const sx = scale ? t + (u * from.width) / to.width : 1;
// const sy = scale ? t + (u * from.height) / to.height : 1;
return `transform: ${transform} translate(${x}px, ${y}px);`;
},
};
}

View File

@ -4,6 +4,7 @@ class Files {
file: File; file: File;
to: string; to: string;
blobUrl: string; blobUrl: string;
id: string;
}[] }[]
>([]); >([]);
public conversionTypes = $state<string[]>([]); public conversionTypes = $state<string[]>([]);

View File

@ -13,6 +13,7 @@
file: f, file: f,
to: converters[0].supportedFormats[0], to: converters[0].supportedFormats[0],
blobUrl: URL.createObjectURL(f), blobUrl: URL.createObjectURL(f),
id: Math.random().toString(36).substring(2),
})), })),
]; ];

View File

@ -1,64 +1,138 @@
<script> <script lang="ts">
import { blur, duration, flip, transition } from "$lib/animation";
import ProgressiveBlur from "$lib/components/visual/effects/ProgressiveBlur.svelte"; import ProgressiveBlur from "$lib/components/visual/effects/ProgressiveBlur.svelte";
import { converters } from "$lib/converters"; import { converters } from "$lib/converters";
import { files } from "$lib/store/index.svelte"; import { files } from "$lib/store/index.svelte";
import clsx from "clsx";
import { XIcon } from "lucide-svelte";
import { onMount } from "svelte";
import { quintOut } from "svelte/easing";
if (files.files.length > 0) files.beenToConverterPage = true; const reversed = $derived(files.files.slice().reverse());
let finisheds = $state(
Array.from({ length: files.files.length }, () => false),
);
onMount(() => {
finisheds.forEach((_, i) => {
const duration = 750 + i * 50 - 8;
setTimeout(() => {
finisheds[i] = true;
console.log(`finished ${i}`);
}, duration);
});
});
</script> </script>
<div class="flex flex-col gap-4 w-full items-center"> <div class="grid grid-cols-1 grid-rows-1 w-full">
{#if files.files.length === 0} {#if files.files.length === 0}
<p class="text-foreground-muted"> <p class="text-foreground-muted col-start-1 row-start-1 text-center">
No files uploaded. Head to the Upload tab to begin! No files uploaded. Head to the Upload tab to begin!
</p> </p>
{/if} {/if}
{#each files.files.reverse() as file, i} <div
<div class="flex flex-col gap-4 w-full items-center col-start-1 row-start-1"
class="flex relative items-center w-full border-2 border-solid border-foreground-muted-alt rounded-xl pl-4 pr-2 py-2" >
> {#each reversed as file, i (file.id)}
<div class="flex items-center w-full z-50 relative">
<div
class="flex items-center flex-grow"
style="text-shadow: 0px 0px 6px white, 0px 0px 12px white"
>
{file.file.name}
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<span>from</span>
<span
class="py-2 px-3 font-display bg-foreground text-background rounded-xl"
>.{file.file.name.split(".").slice(-1)}</span
>
<span>to</span>
<select
class="font-display border-2 border-solid border-foreground-muted-alt rounded-xl p-2 focus:!outline-none"
bind:value={files.conversionTypes[i]}
>
{#each converters[0].supportedFormats as conversionType}
<option value={conversionType}
>{conversionType}</option
>
{/each}
</select>
</div>
</div>
<!-- god knows why, but setting opacity > 0.98 causes a z-ordering issue in firefox ??? -->
<div <div
class="absolute top-0 -z-50 left-0 w-full h-full rounded-[10px] overflow-hidden opacity-[0.98]" animate:flip={{ duration, easing: quintOut }}
out:blur={{
duration,
easing: quintOut,
blurMultiplier: 16,
}}
class={clsx(
"flex relative items-center w-full border-2 border-solid border-foreground-muted-alt rounded-xl pl-4 pr-2 py-2",
{
"initial-fade": !finisheds[i],
},
)}
style="--delay: {i *
50}ms; --transition: {transition}; --duration: {duration}ms;"
> >
<div class="flex items-center w-full z-50 relative">
<div
class="flex items-center flex-grow"
style="text-shadow: 0px 0px 6px white, 0px 0px 12px white"
>
{file.file.name}
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<span>from</span>
<span
class="py-2 px-3 font-display bg-foreground text-background rounded-xl"
>.{file.file.name.split(".").slice(-1)}</span
>
<span>to</span>
<select
class="font-display border-2 border-solid border-foreground-muted-alt rounded-xl p-2 focus:!outline-none"
bind:value={files.conversionTypes[i]}
>
{#each converters[0].supportedFormats as conversionType}
<option value={conversionType}
>{conversionType}</option
>
{/each}
</select>
<button
onclick={() => {
// delete the file from the list
files.files = files.files.filter(
(f) => f !== file,
);
}}
class="ml-2 mr-1"
>
<XIcon size="18" />
</button>
</div>
</div>
<!-- god knows why, but setting opacity > 0.98 causes a z-ordering issue in firefox ??? -->
<div <div
class="bg-cover bg-center w-full h-full" class="absolute top-0 -z-50 left-0 w-full h-full rounded-[10px] overflow-hidden opacity-[0.98]"
style="background-image: url({file.blobUrl});" >
></div> <div
<div class="absolute top-0 right-0 w-5/6 h-full"> class="bg-cover bg-center w-full h-full"
<ProgressiveBlur style="background-image: url({file.blobUrl});"
direction="right" ></div>
endIntensity={64} <div class="absolute top-0 right-0 w-5/6 h-full">
iterations={6} <ProgressiveBlur
fadeTo="rgba(255, 255, 255, 0.8)" direction="right"
/> endIntensity={64}
iterations={6}
fadeTo="rgba(255, 255, 255, 0.8)"
/>
</div>
</div> </div>
</div> </div>
</div> {/each}
{/each} </div>
</div> </div>
<style>
@keyframes initial-transition {
0% {
transform: translateY(50px);
filter: blur(16px);
opacity: 0;
}
100% {
transform: translateY(0);
filter: blur(0);
opacity: 1;
}
}
.initial-fade {
animation: initial-transition 750ms var(--delay) ease-out;
animation-timing-function: var(--transition);
opacity: 0;
}
.initial-fade.finished {
animation: none;
opacity: 1 !important;
}
</style>