From 3783a156c146dadbe465ddbae62ce8c1ddb3ef9e Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 9 Oct 2024 23:04:20 +0800 Subject: [PATCH] BottomSheet: more animation --- src/material/BottomSheet.module.css | 8 ++- src/material/BottomSheet.tsx | 93 +++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/material/BottomSheet.module.css b/src/material/BottomSheet.module.css index 8f716e9..410858b 100644 --- a/src/material/BottomSheet.module.css +++ b/src/material/BottomSheet.module.css @@ -43,7 +43,7 @@ &.animated { position: absolute; - transform: none; + transform: translateY(-50%); overflow: hidden; will-change: width, height, top, left; @@ -54,6 +54,12 @@ & * { overflow: hidden; } + + @media (max-width: 560px) { + & { + transform: none; + } + } } &.bottom { diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 92eabc9..690db27 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -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 = (props) => { let element: HTMLDialogElement; @@ -62,13 +62,17 @@ const BottomSheet: ParentComponent = (props) => { }); const onClose = () => { - hero()!.style.visibility = 'unset' + const srcElement = hero(); + if (srcElement) { + srcElement.style.visibility = "unset"; + } + element.close(); setHero(); }; const animatedClose = () => { - const srcElement = hero() + const srcElement = hero(); const endRect = srcElement?.getBoundingClientRect(); if (endRect) { const startRect = element.getBoundingClientRect(); @@ -76,19 +80,88 @@ const BottomSheet: ParentComponent = (props) => { animation.addEventListener("finish", onClose); animation.addEventListener("cancel", onClose); } else { - element.close(); - setHero(); + if (window.innerWidth > 560 && !props.bottomUp) { + onClose(); + return; + } + const animation = props.bottomUp + ? animateSlideInFromBottom(element, true) + : animateSlideInFromRight(element, true); + animation.addEventListener("finish", onClose); + animation.addEventListener("cancel", onClose); } }; const animatedOpen = () => { element.showModal(); - const srcElement = hero() + const srcElement = hero(); const startRect = srcElement?.getBoundingClientRect(); - if (!startRect) return; - srcElement!.style.visibility = 'hidden' - const endRect = element.getBoundingClientRect(); - animateHero(startRect, endRect, element); + if (startRect) { + srcElement!.style.visibility = "hidden"; + const endRect = element.getBoundingClientRect(); + 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 = (