Animate Collapsible in both directions

This commit is contained in:
Matthias Metelka 2022-09-06 22:53:20 +02:00
parent 3642dc6245
commit a26f7ee86b

View file

@ -3,53 +3,86 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { cubicOut } from "svelte/easing"; import { cubicIn, cubicOut } from "svelte/easing";
import { tweened } from "svelte/motion"; import { tweened } from "svelte/motion";
import { removeStyleProperties } from "../lib/styling"; export let duration: number;
export let duration = 300; function dynamicDuration(height: number, factor: number): number {
return 100 + Math.pow(height, 1 / 4) * factor;
}
export let collapse = false; export let collapse = false;
let collapsed = false; let collapsed = false;
let expandHeight: number;
const size = tweened<number>(undefined, { const size = tweened<number>(undefined);
duration,
async function doCollapse(collapse: boolean): Promise<void> {
if (collapse) {
expandHeight = collapsibleElement.clientHeight;
size.set(0, {
duration: duration || dynamicDuration(expandHeight, 25),
easing: cubicOut, easing: cubicOut,
}); });
function doCollapse(collapse: boolean): void {
if (collapse) {
size.set(0);
} else { } else {
collapsed = false; collapsed = false;
size.set(1, { duration: 0 });
}
}
$: doCollapse(collapse); /* Measure height to tween to */
await new Promise(requestAnimationFrame);
await new Promise(requestAnimationFrame);
expandHeight = collapsibleElement.clientHeight;
let collapsibleElement: HTMLElement; animating = true;
let clientHeight: number; size.set(1, {
duration: duration || dynamicDuration(expandHeight, 25),
function updateHeight(percentage: number): void { easing: cubicIn,
collapsibleElement.style.overflow = "hidden"; });
if (percentage === 1) {
removeStyleProperties(collapsibleElement, "height", "overflow");
} else if (percentage === 0) {
collapsed = true;
removeStyleProperties(collapsibleElement, "height", "overflow");
} else {
collapsibleElement.style.height = `${percentage * clientHeight}px`;
} }
} }
$: if (collapsibleElement) { $: if (collapsibleElement) {
updateHeight($size); doCollapse(collapse);
} }
let collapsibleElement: HTMLElement;
$: collapsed = $size === 0;
$: expanded = $size === 1;
$: animating = $size > 0 && !(collapsed || expanded);
$: height = $size * expandHeight;
$: measuring = !(collapsed || animating || expanded)
</script> </script>
<div bind:this={collapsibleElement} class="collapsible" bind:clientHeight> <div
bind:this={collapsibleElement}
class="collapsible"
class:measuring
class:animating
class:expanded
style:--height="{height}px"
>
<slot {collapsed} /> <slot {collapsed} />
</div> </div>
{#if measuring}
<!-- Placeholder while element is absolutely positioned during measurement -->
<div class="dummy" />
{/if}
<style lang="scss">
.collapsible {
&.measuring {
position: absolute;
opacity: 0;
}
&.animating {
overflow: hidden;
height: var(--height);
&.expanded {
overflow: visible;
}
}
}
</style>