mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Animate Collapsible in both directions
This commit is contained in:
parent
3642dc6245
commit
a26f7ee86b
1 changed files with 62 additions and 29 deletions
|
@ -3,53 +3,86 @@ Copyright: Ankitects Pty Ltd and contributors
|
|||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cubicOut } from "svelte/easing";
|
||||
import { cubicIn, cubicOut } from "svelte/easing";
|
||||
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;
|
||||
let collapsed = false;
|
||||
let expandHeight: number;
|
||||
|
||||
const size = tweened<number>(undefined, {
|
||||
duration,
|
||||
easing: cubicOut,
|
||||
});
|
||||
const size = tweened<number>(undefined);
|
||||
|
||||
function doCollapse(collapse: boolean): void {
|
||||
async function doCollapse(collapse: boolean): Promise<void> {
|
||||
if (collapse) {
|
||||
size.set(0);
|
||||
expandHeight = collapsibleElement.clientHeight;
|
||||
size.set(0, {
|
||||
duration: duration || dynamicDuration(expandHeight, 25),
|
||||
easing: cubicOut,
|
||||
});
|
||||
} else {
|
||||
collapsed = false;
|
||||
size.set(1, { duration: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
$: doCollapse(collapse);
|
||||
|
||||
let collapsibleElement: HTMLElement;
|
||||
let clientHeight: number;
|
||||
|
||||
function updateHeight(percentage: number): void {
|
||||
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`;
|
||||
/* Measure height to tween to */
|
||||
await new Promise(requestAnimationFrame);
|
||||
await new Promise(requestAnimationFrame);
|
||||
expandHeight = collapsibleElement.clientHeight;
|
||||
|
||||
animating = true;
|
||||
size.set(1, {
|
||||
duration: duration || dynamicDuration(expandHeight, 25),
|
||||
easing: cubicIn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: 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>
|
||||
|
||||
<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} />
|
||||
</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>
|
||||
|
|
Loading…
Reference in a new issue