diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 2ebed75..130a7a2 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -11,15 +11,6 @@ import { import "./BottomSheet.css"; import { useHeroSignal } from "../platform/anim"; import material from "./material.module.css"; -import { - ANIM_CURVE_ACELERATION, - ANIM_CURVE_DECELERATION, - ANIM_CURVE_STD, -} from "./theme"; -import { - animateSlideInFromRight, - animateSlideOutToRight, -} from "../platform/anim"; export type BottomSheetProps = { open?: boolean; @@ -48,7 +39,7 @@ function composeAnimationFrame( }; } -const MOVE_SPEED = 1600; +const MOVE_SPEED = 1200; const BottomSheet: ParentComponent = (props) => { let element: HTMLDialogElement; @@ -96,16 +87,11 @@ const BottomSheet: ParentComponent = (props) => { onClose(); return; } - const onAnimationEnd = () => { - element.classList.remove("animated"); - onClose(); - }; - element.classList.add("animated"); - animation = props.bottomUp + const animation = props.bottomUp ? animateSlideInFromBottom(element, true) - : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION }); - animation.addEventListener("finish", onAnimationEnd); - animation.addEventListener("cancel", onAnimationEnd); + : animateSlideInFromRight(element, true); + animation.addEventListener("finish", onClose); + animation.addEventListener("cancel", onClose); } }; @@ -123,18 +109,37 @@ const BottomSheet: ParentComponent = (props) => { } else if (props.bottomUp) { animateSlideInFromBottom(element); } else if (window.innerWidth <= 560) { - 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); + 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("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 = ( element: HTMLElement, reserve?: boolean, @@ -171,19 +176,13 @@ const BottomSheet: ParentComponent = (props) => { element: HTMLElement, reserve?: boolean, ) => { - const easing = ANIM_CURVE_STD; + const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; element.classList.add("animated"); - // distance_lt = (|top_start - top_end|^2 + |left_end - left_end|^2)^(-2) - const distancelt = Math.sqrt( + const distance = Math.sqrt( Math.pow(Math.abs(startRect.top - endRect.top), 2) + - Math.pow(Math.abs(startRect.left - endRect.left), 2), + Math.pow(Math.abs(startRect.left - startRect.top), 2), ); - const distancerb = Math.sqrt( - Math.pow(Math.abs(startRect.bottom - endRect.bottom), 2) + - Math.pow(Math.abs(startRect.right - endRect.right), 2), - ); - const distance = distancelt + distancerb; - const duration = distance / 1.6; + const duration = (distance / MOVE_SPEED) * 1000; animation = element.animate( [ composeAnimationFrame(startRect, { transform: "none" }), diff --git a/src/material/Menu.tsx b/src/material/Menu.tsx index a80a423..04b8d86 100644 --- a/src/material/Menu.tsx +++ b/src/material/Menu.tsx @@ -13,12 +13,10 @@ import { animateShrinkToTopRight, } from "../platform/anim"; -type Anchor = Pick - type Props = { open?: boolean; onClose?: JSX.EventHandlerUnion; - anchor: () => Anchor; + anchor: () => DOMRect; }; function px(n?: number) { diff --git a/src/platform/anim.ts b/src/platform/anim.ts index dfffc87..3dd0ead 100644 --- a/src/platform/anim.ts +++ b/src/platform/anim.ts @@ -49,7 +49,7 @@ export function useHeroSignal( return [get, set]; } else { - console.debug("no hero source"); + console.debug("no hero source") return [() => undefined, () => undefined]; } } @@ -85,6 +85,7 @@ export function animateRollOutFromTop( return animation; } + export function animateRollInFromBottom( root: HTMLElement, options?: Omit, @@ -125,10 +126,8 @@ export function animateGrowFromTopRight( const { width, height } = root.getBoundingClientRect(); - const speed = transitionSpeedForEnter(window.innerHeight); - - const durationX = Math.floor(height / speed); - const durationY = Math.floor(width / speed); + 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)% @@ -138,19 +137,20 @@ export function animateGrowFromTopRight( const centerOffset = minDuration / maxDuration; const keyframes = [ - { transform: "scaleX(0.5)", opacity: 0, height: "0px", offset: 0 }, + { transform: "scaleX(0)", opacity: 0, height: "0px", offset: 0 }, { - transform: `scaleX(${minDuration === durationX ? "1" : centerOffset / 2 + 0.5})`, + transform: `scaleX(${minDuration === durationX ? "1" : centerOffset})`, height: `${(minDuration === durationY ? 1 : centerOffset) * height}px`, offset: centerOffset, + opacity: 1, }, - { transform: "scaleX(1)", height: `${height}px`, opacity: 1, offset: 1 }, + { transform: "scaleX(1)", height: `${height}px`, offset: 1 }, ]; - const animation = root.animate(keyframes, { - ...options, - duration: maxDuration, - }); + const animation = root.animate( + keyframes, + { ...options, duration: maxDuration }, + ); const restore = () => { root.style.transformOrigin = transformOrigin; @@ -173,9 +173,9 @@ export function animateShrinkToTopRight( const { width, height } = root.getBoundingClientRect(); - const speed = transitionSpeedForLeave(window.innerWidth); - - const duration = Math.floor(Math.max(width / speed, height / speed)); + const duration = Math.floor( + Math.max((width / 1600) * 1000, (height / 1600) * 1000), + ); const animation = root.animate( { @@ -195,105 +195,3 @@ export function animateShrinkToTopRight( 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, -) { - 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, -) { - 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; -} diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index 4becc5b..9015e8b 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -26,14 +26,10 @@ import { Close, Edit, ExpandMore, - Group, MoreVert, OpenInBrowser, - PersonOff, - PlaylistAdd, Send, Share, - Translate, Verified, } from "@suid/icons-material"; import { Title } from "../material/typography"; @@ -94,10 +90,6 @@ const Profile: Component = () => { console.error(err); }); - const isCurrentSessionProfile = () => { - return session().account?.inf?.url === profile()?.url; - }; - const [recentTootFilter, setRecentTootFilter] = createSignal({ pinned: true, boost: false, @@ -269,48 +261,18 @@ const Profile: Component = () => { document.getElementById(menuButId)!.getBoundingClientRect() } > - - - - - Subscribe... - - } - > - - - - - Edit... - - + + + + + Edit... + - - - - - Subscribers - - - - - - Blocklist - - - - - - Translate Name and Bio... - - Mention in... + Mention {profile()?.displayName || ""}... { - 0}> + >(props: Props) { ); }; - let anchor: { left: number; top: number; right: number }; - - const onClick = (event: MouseEvent) => { - anchor = { - left: event.clientX, - right: event.clientX, - top: event.clientY, - }; - setOpen(true); - }; - return ( <> - - anchor}> + + document.getElementById(buttonId)!.getBoundingClientRect() + } + > {(item, idx) => ( <>