Menu: improve animation for top-right
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				/ depoly (push) Successful in 1m18s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	/ depoly (push) Successful in 1m18s
				
			This commit is contained in:
		
							parent
							
								
									bafa8f4e24
								
							
						
					
					
						commit
						10b517bceb
					
				
					 3 changed files with 151 additions and 29 deletions
				
			
		| 
						 | 
				
			
			@ -8,6 +8,10 @@ import {
 | 
			
		|||
} from "solid-js";
 | 
			
		||||
import { ANIM_CURVE_STD } from "./theme";
 | 
			
		||||
import "./Menu.css";
 | 
			
		||||
import {
 | 
			
		||||
  animateGrowFromTopRight,
 | 
			
		||||
  animateShrinkToTopRight,
 | 
			
		||||
} from "../platform/anim";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  open?: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,23 +55,7 @@ const Menu: ParentComponent<Props> = (props) => {
 | 
			
		|||
            top,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          const overflow = root.style.overflow;
 | 
			
		||||
          root.style.overflow = "hidden";
 | 
			
		||||
          const duration = (rend.height / 1600) * 1000;
 | 
			
		||||
          const easing = ANIM_CURVE_STD;
 | 
			
		||||
          const animation = root.animate(
 | 
			
		||||
            {
 | 
			
		||||
              height: [`${rend.height / 2}px`, `${rend.height}px`],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              duration,
 | 
			
		||||
              easing,
 | 
			
		||||
            },
 | 
			
		||||
          );
 | 
			
		||||
          animation.addEventListener(
 | 
			
		||||
            "finish",
 | 
			
		||||
            () => (root.style.overflow = overflow),
 | 
			
		||||
          );
 | 
			
		||||
          animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
 | 
			
		||||
        } else {
 | 
			
		||||
          openAnimationOrigin = "lt";
 | 
			
		||||
          setAnchorPos({ left, top });
 | 
			
		||||
| 
						 | 
				
			
			@ -119,19 +107,10 @@ const Menu: ParentComponent<Props> = (props) => {
 | 
			
		|||
        root.close();
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      const overflow = root.style.overflow;
 | 
			
		||||
      root.style.overflow = "hidden";
 | 
			
		||||
      const animation = root.animate(
 | 
			
		||||
        {
 | 
			
		||||
          height: [`${rend.height}px`, `${rend.height / 2}px`],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          duration: (rend.height / 2 / 1600) * 1000,
 | 
			
		||||
          easing: ANIM_CURVE_STD,
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
      const animation = animateShrinkToTopRight(root, {
 | 
			
		||||
        easing: ANIM_CURVE_STD,
 | 
			
		||||
      });
 | 
			
		||||
      animation.addEventListener("finish", () => {
 | 
			
		||||
        root.style.overflow = overflow;
 | 
			
		||||
        root.close();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,6 +117,7 @@
 | 
			
		|||
  --tutu-shadow-e24: 0px 24px 48px 0px var(--tutu-color-shadow-l2);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /* curves are also hard-coded in theme.ts */
 | 
			
		||||
  --tutu-anim-curve-std: cubic-bezier(0.4, 0, 0.2, 1);
 | 
			
		||||
  --tutu-anim-curve-deceleration: cubic-bezier(0, 0, 0.2, 1);
 | 
			
		||||
  --tutu-anim-curve-aceleration: cubic-bezier(0.4, 0, 1, 1);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,3 +53,145 @@ export function useHeroSignal(
 | 
			
		|||
    return [() => undefined, () => undefined];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function animateRollOutFromTop(
 | 
			
		||||
  root: HTMLElement,
 | 
			
		||||
  options?: Omit<KeyframeAnimationOptions, "duration">,
 | 
			
		||||
) {
 | 
			
		||||
  const overflow = root.style.overflow;
 | 
			
		||||
  root.style.overflow = "hidden";
 | 
			
		||||
 | 
			
		||||
  const { height } = root.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
  const opts = Object.assign(
 | 
			
		||||
    {
 | 
			
		||||
      duration: Math.floor((height / 1600) * 1000),
 | 
			
		||||
    },
 | 
			
		||||
    options,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const animation = root.animate(
 | 
			
		||||
    {
 | 
			
		||||
      height: ["0px", `${height}px`],
 | 
			
		||||
    },
 | 
			
		||||
    opts,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const restore = () => (root.style.overflow = overflow);
 | 
			
		||||
 | 
			
		||||
  animation.addEventListener("finish", restore);
 | 
			
		||||
  animation.addEventListener("cancel", restore);
 | 
			
		||||
 | 
			
		||||
  return animation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function animateRollInFromBottom(
 | 
			
		||||
  root: HTMLElement,
 | 
			
		||||
  options?: Omit<KeyframeAnimationOptions, "duration">,
 | 
			
		||||
) {
 | 
			
		||||
  const overflow = root.style.overflow;
 | 
			
		||||
  root.style.overflow = "hidden";
 | 
			
		||||
 | 
			
		||||
  const { height } = root.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
  const opts = Object.assign(
 | 
			
		||||
    {
 | 
			
		||||
      duration: Math.floor((height / 1600) * 1000),
 | 
			
		||||
    },
 | 
			
		||||
    options,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const animation = root.animate(
 | 
			
		||||
    {
 | 
			
		||||
      height: [`${height}px`, "0px"],
 | 
			
		||||
    },
 | 
			
		||||
    opts,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const restore = () => (root.style.overflow = overflow);
 | 
			
		||||
 | 
			
		||||
  animation.addEventListener("finish", restore);
 | 
			
		||||
  animation.addEventListener("cancel", restore);
 | 
			
		||||
 | 
			
		||||
  return animation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function animateGrowFromTopRight(
 | 
			
		||||
  root: HTMLElement,
 | 
			
		||||
  options?: KeyframeAnimationOptions,
 | 
			
		||||
) {
 | 
			
		||||
  const transformOrigin = root.style.transformOrigin;
 | 
			
		||||
  root.style.transformOrigin = "top right";
 | 
			
		||||
 | 
			
		||||
  const { width, height } = root.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
  const durationX = Math.floor((height / 1600) * 1000);
 | 
			
		||||
  const durationY = Math.floor((width / 1600) * 1000);
 | 
			
		||||
 | 
			
		||||
  // finds the offset for the center frame,
 | 
			
		||||
  // it will stops at the (minDuration / maxDuration)%
 | 
			
		||||
  const minDuration = Math.min(durationX, durationY);
 | 
			
		||||
  const maxDuration = Math.max(durationX, durationY);
 | 
			
		||||
 | 
			
		||||
  const centerOffset = minDuration / maxDuration;
 | 
			
		||||
 | 
			
		||||
  const keyframes = [
 | 
			
		||||
    { transform: "scaleX(0)", opacity: 0, height: "0px", offset: 0 },
 | 
			
		||||
    {
 | 
			
		||||
      transform: `scaleX(${minDuration === durationX ? "1" : centerOffset})`,
 | 
			
		||||
      height: `${(minDuration === durationY ? 1 : centerOffset) * height}px`,
 | 
			
		||||
      offset: centerOffset,
 | 
			
		||||
      opacity: 1,
 | 
			
		||||
    },
 | 
			
		||||
    { transform: "scaleX(1)", height: `${height}px`, offset: 1 },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const animation = root.animate(
 | 
			
		||||
    keyframes,
 | 
			
		||||
    { ...options, duration: maxDuration },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const restore = () => {
 | 
			
		||||
    root.style.transformOrigin = transformOrigin;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  animation.addEventListener("cancel", restore);
 | 
			
		||||
  animation.addEventListener("finish", restore);
 | 
			
		||||
 | 
			
		||||
  return animation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function animateShrinkToTopRight(
 | 
			
		||||
  root: HTMLElement,
 | 
			
		||||
  options?: KeyframeAnimationOptions,
 | 
			
		||||
) {
 | 
			
		||||
  const overflow = root.style.overflow;
 | 
			
		||||
  root.style.overflow = "hidden";
 | 
			
		||||
  const transformOrigin = root.style.transformOrigin;
 | 
			
		||||
  root.style.transformOrigin = "top right";
 | 
			
		||||
 | 
			
		||||
  const { width, height } = root.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
  const duration = Math.floor(
 | 
			
		||||
    Math.max((width / 1600) * 1000, (height / 1600) * 1000),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const animation = root.animate(
 | 
			
		||||
    {
 | 
			
		||||
      transform: ["scale(1)", "scale(0.5)"],
 | 
			
		||||
      opacity: [1, 0],
 | 
			
		||||
    },
 | 
			
		||||
    { ...options, duration },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const restore = () => {
 | 
			
		||||
    root.style.overflow = overflow;
 | 
			
		||||
    root.style.transformOrigin = transformOrigin;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  animation.addEventListener("cancel", restore);
 | 
			
		||||
  animation.addEventListener("finish", restore);
 | 
			
		||||
 | 
			
		||||
  return animation;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue