diff --git a/frontend/code/components/revealer.ts b/frontend/code/components/revealer.ts index 3bb9907d..be8736c7 100644 --- a/frontend/code/components/revealer.ts +++ b/frontend/code/components/revealer.ts @@ -4,6 +4,11 @@ import { ComponentId, TextStyle } from "../dataModels"; import { commitCss } from "../utils"; import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; import { RippleEffect } from "../rippleEffect"; +import { + RioAnimation, + RioAnimationPlayback, + RioKeyframeAnimation, +} from "../animations"; let HEADER_PADDING: number = 0.3; @@ -23,6 +28,7 @@ export class RevealerComponent extends ComponentBase { private contentInnerElement: HTMLElement; private rippleInstance: RippleEffect; + private currentAnimation: RioAnimationPlayback | null = null; createElement(): HTMLElement { // Create the HTML @@ -171,24 +177,28 @@ export class RevealerComponent extends ComponentBase { return; } - // Update the CSS to trigger the expand animation - this.contentOuterElement.style.maxHeight = "0"; - this.element.classList.add("rio-revealer-open"); - // The components may currently be in flux due to a pending re-layout. // If that is the case, reading the `scrollHeight` would lead to an // incorrect value. Wait for the resize to finish before fetching it. requestAnimationFrame(() => { - // Animating max-height only works with fixed values (and not - // `unset`, etc), so we have to assign the child's exact height in - // pixels - this.setMaxHeightToChildHeight(); + // Cancel the current animation, if any + if (this.currentAnimation !== null) { + this.currentAnimation.cancel(); + } - // Once the animation is finished, remove the max-height so that the - // child component can freely resize itself - setTimeout(() => { + // Start the open animation + let animation = this.makeAnimation("open"); + this.currentAnimation = animation.animate(this.contentOuterElement); + + // The animation requires a specific height to animate to, but this + // prevents the content from resizing itself. So at the end of the + // animation, remove the `max-height`. + this.currentAnimation.addEventListener("end", () => { this.contentOuterElement.style.maxHeight = "unset"; - }, 1000 * 0.25); + }); + + // Update the CSS to trigger all the other animations + this.element.classList.add("rio-revealer-open"); }); } @@ -198,21 +208,42 @@ export class RevealerComponent extends ComponentBase { return; } - // Again, animating from `max-height: unset` doesn't work, so we have to - // set it to the child's size in pixels - this.setMaxHeightToChildHeight(); - commitCss(this.contentOuterElement); + // Cancel the current animation, if any + if (this.currentAnimation !== null) { + this.currentAnimation.cancel(); + } + + // Start the close animation + let animation = this.makeAnimation("close"); + this.currentAnimation = animation.animate(this.contentOuterElement); this.element.classList.remove("rio-revealer-open"); - this.contentOuterElement.style.maxHeight = "0"; } - private setMaxHeightToChildHeight(): void { + private makeAnimation(mode: "open" | "close"): RioAnimation { + let keyframes = [ + { maxHeight: "0" }, + // Animating to/from "unset" doesn't work, so we need to obtain the + // actual height + { maxHeight: this.getHeightForAnimation() }, + ]; + + if (mode === "close") { + keyframes.reverse(); + } + + return new RioKeyframeAnimation(keyframes, { + duration: 250, + easing: "ease-in-out", + }); + } + + private getHeightForAnimation(): string { let contentHeight = this.contentInnerElement.scrollHeight; let selfHeight = this.element.scrollHeight; let headerHeight = this.headerElement.scrollHeight; let targetHeight = Math.max(contentHeight, selfHeight - headerHeight); - this.contentOuterElement.style.maxHeight = `${targetHeight}px`; + return `${targetHeight}px`; } } diff --git a/frontend/css/components/revealer.scss b/frontend/css/components/revealer.scss index 74293d53..7e81c884 100644 --- a/frontend/css/components/revealer.scss +++ b/frontend/css/components/revealer.scss @@ -40,9 +40,7 @@ .rio-revealer-content-outer { flex-grow: 1; - overflow: hidden; - - transition: max-height 0.25s ease-in-out; + overflow: hidden; // Required for the open/close animation } .rio-revealer-content-inner {