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";
|
} from "solid-js";
|
||||||
import { ANIM_CURVE_STD } from "./theme";
|
import { ANIM_CURVE_STD } from "./theme";
|
||||||
import "./Menu.css";
|
import "./Menu.css";
|
||||||
|
import {
|
||||||
|
animateGrowFromTopRight,
|
||||||
|
animateShrinkToTopRight,
|
||||||
|
} from "../platform/anim";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
|
@ -51,23 +55,7 @@ const Menu: ParentComponent<Props> = (props) => {
|
||||||
top,
|
top,
|
||||||
});
|
});
|
||||||
|
|
||||||
const overflow = root.style.overflow;
|
animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
|
||||||
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),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
openAnimationOrigin = "lt";
|
openAnimationOrigin = "lt";
|
||||||
setAnchorPos({ left, top });
|
setAnchorPos({ left, top });
|
||||||
|
@ -119,19 +107,10 @@ const Menu: ParentComponent<Props> = (props) => {
|
||||||
root.close();
|
root.close();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const overflow = root.style.overflow;
|
const animation = animateShrinkToTopRight(root, {
|
||||||
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,
|
easing: ANIM_CURVE_STD,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
animation.addEventListener("finish", () => {
|
animation.addEventListener("finish", () => {
|
||||||
root.style.overflow = overflow;
|
|
||||||
root.close();
|
root.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
--tutu-shadow-e24: 0px 24px 48px 0px var(--tutu-color-shadow-l2);
|
--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-std: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--tutu-anim-curve-deceleration: cubic-bezier(0, 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);
|
--tutu-anim-curve-aceleration: cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
|
|
@ -53,3 +53,145 @@ export function useHeroSignal(
|
||||||
return [() => undefined, () => undefined];
|
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…
Reference in a new issue