BottomSheet: move slides animations to platform

This commit is contained in:
thislight 2024-11-03 17:36:03 +08:00
parent 10b517bceb
commit b61012f12b
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
2 changed files with 121 additions and 33 deletions

View file

@ -11,6 +11,8 @@ import {
import "./BottomSheet.css"; import "./BottomSheet.css";
import { useHeroSignal } from "../platform/anim"; import { useHeroSignal } from "../platform/anim";
import material from "./material.module.css"; import material from "./material.module.css";
import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme";
import { animateSlideInFromRight, animateSlideOutToRight } from "../platform/anim";
export type BottomSheetProps = { export type BottomSheetProps = {
open?: boolean; open?: boolean;
@ -39,7 +41,7 @@ function composeAnimationFrame(
}; };
} }
const MOVE_SPEED = 1200; const MOVE_SPEED = 1600;
const BottomSheet: ParentComponent<BottomSheetProps> = (props) => { const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
let element: HTMLDialogElement; let element: HTMLDialogElement;
@ -87,11 +89,16 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
onClose(); onClose();
return; return;
} }
const animation = props.bottomUp const onAnimationEnd = () => {
element.classList.remove("animated")
onClose()
}
element.classList.add("animated")
animation = props.bottomUp
? animateSlideInFromBottom(element, true) ? animateSlideInFromBottom(element, true)
: animateSlideInFromRight(element, true); : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION });
animation.addEventListener("finish", onClose); animation.addEventListener("finish", onAnimationEnd);
animation.addEventListener("cancel", onClose); animation.addEventListener("cancel", onAnimationEnd);
} }
}; };
@ -109,37 +116,16 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
} else if (props.bottomUp) { } else if (props.bottomUp) {
animateSlideInFromBottom(element); animateSlideInFromBottom(element);
} else if (window.innerWidth <= 560) { } else if (window.innerWidth <= 560) {
animateSlideInFromRight(element); element.classList.add("animated")
const onAnimationEnd = () => {
element.classList.remove("animated")
}
animation = animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION });
animation.addEventListener("finish", onAnimationEnd)
animation.addEventListener("cancel", onAnimationEnd)
} }
}; };
const animateSlideInFromRight = (element: HTMLElement, reserve?: boolean) => {
const rect = element.getBoundingClientRect();
const easing = "cubic-bezier(0.4, 0, 0.2, 1)";
element.classList.add("animated");
const oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const distance = Math.abs(rect.left - window.innerWidth);
const duration = (distance / MOVE_SPEED) * 1000;
animation = element.animate(
{
left: reserve
? [`${rect.left}px`, `${window.innerWidth}px`]
: [`${window.innerWidth}px`, `${rect.left}px`],
},
{ easing, duration },
);
const onAnimationEnd = () => {
element.classList.remove("animated");
document.body.style.overflow = oldOverflow;
animation = undefined;
};
animation.addEventListener("cancel", onAnimationEnd);
animation.addEventListener("finish", onAnimationEnd);
return animation;
};
const animateSlideInFromBottom = ( const animateSlideInFromBottom = (
element: HTMLElement, element: HTMLElement,
reserve?: boolean, reserve?: boolean,

View file

@ -195,3 +195,105 @@ export function animateShrinkToTopRight(
return animation; return animation;
} }
// Contribution to the animation speed:
// - the screen size: mobiles should have longer transition,
// the transition time should be longer as the travelling distance longer,
// but it's not linear. The larger screen should have higher velocity,
// to avoid the transition is too long.
// As the screen larger, on desktops, the transition should be simpler and
// signficantly faster.
// On much smaller screens, like wearables, the transition should be shorter
// than on mobiles.
// - Animation complexity: On mobile:
// - large, complex, full-screen transitions may have longer durations, over 375ms
// - entering screen over 225ms
// - leaving screen over 195ms
function transitionSpeedForEnter(innerWidth: number) {
if (innerWidth < 300) {
return 2.4;
} else if (innerWidth < 560) {
return 1.6;
} else if (innerWidth < 1200) {
return 2.4;
} else {
return 2.55;
}
}
function transitionSpeedForLeave(innerWidth: number) {
if (innerWidth < 300) {
return 2.8;
} else if (innerWidth < 560) {
return 1.96;
} else if (innerWidth < 1200) {
return 2.8;
} else {
return 2.55;
}
}
export function animateSlideInFromRight(
root: HTMLElement,
options?: Omit<KeyframeAnimationOptions, "duration">,
) {
const { left } = root.getBoundingClientRect();
const { innerWidth } = window;
const oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const distance = Math.abs(left - innerWidth);
const duration = Math.floor(distance / transitionSpeedForEnter(innerWidth));
const opts = Object.assign({ duration }, options);
const animation = root.animate(
{
left: [`${innerWidth}px`, `${left}px`],
},
opts,
);
const restore = () => {
document.body.style.overflow = oldOverflow;
};
animation.addEventListener("cancel", restore);
animation.addEventListener("finish", restore);
return animation;
}
export function animateSlideOutToRight(
root: HTMLElement,
options?: Omit<KeyframeAnimationOptions, "duration">,
) {
const { left } = root.getBoundingClientRect();
const { innerWidth } = window;
const oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const distance = Math.abs(left - innerWidth);
const duration = Math.floor(distance / transitionSpeedForLeave(innerWidth));
const opts = Object.assign({ duration }, options);
const animation = root.animate(
{
left: [`${left}px`, `${innerWidth}px`],
},
opts,
);
const restore = () => {
document.body.style.overflow = oldOverflow;
};
animation.addEventListener("cancel", restore);
animation.addEventListener("finish", restore);
return animation;
}