mirror of https://github.com/VERT-sh/VERT.git
feat: prettier animations
This commit is contained in:
parent
db5d5414db
commit
cd91b30119
|
@ -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);`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ class Files {
|
|||
file: File;
|
||||
to: string;
|
||||
blobUrl: string;
|
||||
id: string;
|
||||
}[]
|
||||
>([]);
|
||||
public conversionTypes = $state<string[]>([]);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
file: f,
|
||||
to: converters[0].supportedFormats[0],
|
||||
blobUrl: URL.createObjectURL(f),
|
||||
id: Math.random().toString(36).substring(2),
|
||||
})),
|
||||
];
|
||||
|
||||
|
|
|
@ -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 { converters } from "$lib/converters";
|
||||
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>
|
||||
|
||||
<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}
|
||||
<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!
|
||||
</p>
|
||||
{/if}
|
||||
{#each files.files.reverse() as file, i}
|
||||
<div
|
||||
class="flex relative items-center w-full border-2 border-solid border-foreground-muted-alt rounded-xl pl-4 pr-2 py-2"
|
||||
>
|
||||
<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
|
||||
class="flex flex-col gap-4 w-full items-center col-start-1 row-start-1"
|
||||
>
|
||||
{#each reversed as file, i (file.id)}
|
||||
<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
|
||||
class="bg-cover bg-center w-full h-full"
|
||||
style="background-image: url({file.blobUrl});"
|
||||
></div>
|
||||
<div class="absolute top-0 right-0 w-5/6 h-full">
|
||||
<ProgressiveBlur
|
||||
direction="right"
|
||||
endIntensity={64}
|
||||
iterations={6}
|
||||
fadeTo="rgba(255, 255, 255, 0.8)"
|
||||
/>
|
||||
class="absolute top-0 -z-50 left-0 w-full h-full rounded-[10px] overflow-hidden opacity-[0.98]"
|
||||
>
|
||||
<div
|
||||
class="bg-cover bg-center w-full h-full"
|
||||
style="background-image: url({file.blobUrl});"
|
||||
></div>
|
||||
<div class="absolute top-0 right-0 w-5/6 h-full">
|
||||
<ProgressiveBlur
|
||||
direction="right"
|
||||
endIntensity={64}
|
||||
iterations={6}
|
||||
fadeTo="rgba(255, 255, 255, 0.8)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</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>
|
||||
|
|
Loading…
Reference in New Issue