BottomSheet: more animation

This commit is contained in:
thislight 2024-10-09 23:04:20 +08:00
parent 7d3c97c56b
commit 4fb5582b3d
2 changed files with 90 additions and 11 deletions

View file

@ -43,7 +43,7 @@
&.animated { &.animated {
position: absolute; position: absolute;
transform: none; transform: translateY(-50%);
overflow: hidden; overflow: hidden;
will-change: width, height, top, left; will-change: width, height, top, left;
@ -54,6 +54,12 @@
& * { & * {
overflow: hidden; overflow: hidden;
} }
@media (max-width: 560px) {
& {
transform: none;
}
}
} }
&.bottom { &.bottom {

View file

@ -36,7 +36,7 @@ function composeAnimationFrame(
}; };
} }
const MOVE_SPEED = 1400; // 1400px/s, bottom sheet is big and a bit heavier than small papers const MOVE_SPEED = 1200;
const BottomSheet: ParentComponent<BottomSheetProps> = (props) => { const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
let element: HTMLDialogElement; let element: HTMLDialogElement;
@ -62,13 +62,17 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
}); });
const onClose = () => { const onClose = () => {
hero()!.style.visibility = 'unset' const srcElement = hero();
if (srcElement) {
srcElement.style.visibility = "unset";
}
element.close(); element.close();
setHero(); setHero();
}; };
const animatedClose = () => { const animatedClose = () => {
const srcElement = hero() const srcElement = hero();
const endRect = srcElement?.getBoundingClientRect(); const endRect = srcElement?.getBoundingClientRect();
if (endRect) { if (endRect) {
const startRect = element.getBoundingClientRect(); const startRect = element.getBoundingClientRect();
@ -76,19 +80,88 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
animation.addEventListener("finish", onClose); animation.addEventListener("finish", onClose);
animation.addEventListener("cancel", onClose); animation.addEventListener("cancel", onClose);
} else { } else {
element.close(); if (window.innerWidth > 560 && !props.bottomUp) {
setHero(); onClose();
return;
}
const animation = props.bottomUp
? animateSlideInFromBottom(element, true)
: animateSlideInFromRight(element, true);
animation.addEventListener("finish", onClose);
animation.addEventListener("cancel", onClose);
} }
}; };
const animatedOpen = () => { const animatedOpen = () => {
element.showModal(); element.showModal();
const srcElement = hero() const srcElement = hero();
const startRect = srcElement?.getBoundingClientRect(); const startRect = srcElement?.getBoundingClientRect();
if (!startRect) return; if (startRect) {
srcElement!.style.visibility = 'hidden' srcElement!.style.visibility = "hidden";
const endRect = element.getBoundingClientRect(); const endRect = element.getBoundingClientRect();
animateHero(startRect, endRect, element); animateHero(startRect, endRect, element);
} else if (props.bottomUp) {
animateSlideInFromBottom(element);
} else if (window.innerWidth <= 560) {
animateSlideInFromRight(element);
}
};
const animateSlideInFromRight = (element: HTMLElement, reserve?: boolean) => {
const rect = element.getBoundingClientRect();
const easing = "cubic-bezier(0.4, 0, 0.2, 1)";
element.classList.add(styles.animated);
const distance = Math.abs(rect.left - window.innerWidth);
const duration = (distance / MOVE_SPEED) * 1000;
animation = element.animate(
{
top: [rect.top, rect.top],
left: reserve
? [`${rect.left}px`, `${window.innerWidth}px`]
: [`${window.innerWidth}px`, `${rect.left}px`],
width: [rect.width, rect.width],
height: [rect.height, rect.height],
},
{ easing, duration },
);
const onAnimationEnd = () => {
element.classList.remove(styles.animated);
animation = undefined;
};
animation.addEventListener("cancel", onAnimationEnd);
animation.addEventListener("finish", onAnimationEnd);
return animation;
};
const animateSlideInFromBottom = (
element: HTMLElement,
reserve?: boolean,
) => {
const rect = element.getBoundingClientRect();
const easing = "cubic-bezier(0.4, 0, 0.2, 1)";
element.classList.add(styles.animated);
const distance = Math.abs(rect.top - window.innerHeight);
const duration = (distance / MOVE_SPEED) * 1000;
animation = element.animate(
{
left: [rect.left, rect.left],
top: reserve
? [`${rect.top}px`, `${window.innerHeight}px`]
: [`${window.innerHeight}px`, `${rect.top}px`],
width: [rect.width, rect.width],
height: [rect.height, rect.height],
},
{ easing, duration },
);
const onAnimationEnd = () => {
element.classList.remove(styles.animated);
animation = undefined;
};
animation.addEventListener("cancel", onAnimationEnd);
animation.addEventListener("finish", onAnimationEnd);
return animation;
}; };
const animateHero = ( const animateHero = (