diff --git a/src/lib/animation/index.ts b/src/lib/animation/index.ts index 3d12513..c574e9b 100644 --- a/src/lib/animation/index.ts +++ b/src/lib/animation/index.ts @@ -1,4 +1,6 @@ import type { EasingFunction, TransitionConfig } from "svelte/transition"; +import type { AnimationConfig, FlipParams } from "svelte/animate"; +import { cubicOut } from "svelte/easing"; 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)"; @@ -58,13 +60,15 @@ export const blur = ( "(prefers-reduced-motion: reduce)", ).matches; if (typeof config?.opacity === "undefined" && config) config.opacity = true; + const isUsingTranslate = !!config?.x || !!config?.y || !!config?.scale; + console.log(isUsingTranslate); return { delay: config?.delay || 0, duration: prefersReducedMotion ? 0 : config?.duration || 300, - css: (t) => - prefersReducedMotion - ? "" - : `filter: blur(${(1 - t) * (config?.blurMultiplier || 1)}px); opacity: ${config?.opacity ? t : 1}; transform: translate(${remap( + css: (t) => { + if (prefersReducedMotion) return ""; + const translate = isUsingTranslate + ? `translate(${remap( t, 0, 1, @@ -112,7 +116,57 @@ export const blur = ( config?.scale?.end, config?.scale?.start, ), - )});`, + )})` + : ``; + return `filter: blur(${(1 - t) * (config?.blurMultiplier || 1)}px); opacity: ${config?.opacity ? t : 1}; transform: ${ + translate + };`; + }, 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);`; + }, + }; +} diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts index ce27efc..f8ab7c1 100644 --- a/src/lib/store/index.svelte.ts +++ b/src/lib/store/index.svelte.ts @@ -4,6 +4,7 @@ class Files { file: File; to: string; blobUrl: string; + id: string; }[] >([]); public conversionTypes = $state([]); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 009a297..8bfe7f2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,6 +13,7 @@ file: f, to: converters[0].supportedFormats[0], blobUrl: URL.createObjectURL(f), + id: Math.random().toString(36).substring(2), })), ]; diff --git a/src/routes/convert/+page.svelte b/src/routes/convert/+page.svelte index f387130..f68209c 100644 --- a/src/routes/convert/+page.svelte +++ b/src/routes/convert/+page.svelte @@ -1,64 +1,138 @@ - -
+
{#if files.files.length === 0} -

+

No files uploaded. Head to the Upload tab to begin!

{/if} - {#each files.files.reverse() as file, i} -
-
-
- {file.file.name} -
-
- from - .{file.file.name.split(".").slice(-1)} - to - -
-
- +
+ {#each reversed as file, i (file.id)}
+
+
+ {file.file.name} +
+
+ from + .{file.file.name.split(".").slice(-1)} + to + + +
+
+
-
- + class="absolute top-0 -z-50 left-0 w-full h-full rounded-[10px] overflow-hidden opacity-[0.98]" + > +
+
+ +
-
- {/each} + {/each} +
+ +