From b61012f12b795eaf5003a9a23dac9e737d8fe589 Mon Sep 17 00:00:00 2001 From: thislight Date: Sun, 3 Nov 2024 17:36:03 +0800 Subject: [PATCH 1/6] BottomSheet: move slides animations to platform --- src/material/BottomSheet.tsx | 52 +++++++----------- src/platform/anim.ts | 102 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 33 deletions(-) diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 130a7a2..1258c81 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -11,6 +11,8 @@ import { import "./BottomSheet.css"; import { useHeroSignal } from "../platform/anim"; import material from "./material.module.css"; +import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; +import { animateSlideInFromRight, animateSlideOutToRight } from "../platform/anim"; export type BottomSheetProps = { open?: boolean; @@ -39,7 +41,7 @@ function composeAnimationFrame( }; } -const MOVE_SPEED = 1200; +const MOVE_SPEED = 1600; const BottomSheet: ParentComponent = (props) => { let element: HTMLDialogElement; @@ -87,11 +89,16 @@ const BottomSheet: ParentComponent = (props) => { onClose(); return; } - const animation = props.bottomUp + const onAnimationEnd = () => { + element.classList.remove("animated") + onClose() + } + element.classList.add("animated") + animation = props.bottomUp ? animateSlideInFromBottom(element, true) - : animateSlideInFromRight(element, true); - animation.addEventListener("finish", onClose); - animation.addEventListener("cancel", onClose); + : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION }); + animation.addEventListener("finish", onAnimationEnd); + animation.addEventListener("cancel", onAnimationEnd); } }; @@ -109,37 +116,16 @@ const BottomSheet: ParentComponent = (props) => { } else if (props.bottomUp) { animateSlideInFromBottom(element); } 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 = ( element: HTMLElement, reserve?: boolean, diff --git a/src/platform/anim.ts b/src/platform/anim.ts index 3dd0ead..3369e09 100644 --- a/src/platform/anim.ts +++ b/src/platform/anim.ts @@ -195,3 +195,105 @@ 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; +} From 20a5e565b1d544b94d4a2d0d213867b7c05a4918 Mon Sep 17 00:00:00 2001 From: thislight Date: Sun, 3 Nov 2024 18:00:13 +0800 Subject: [PATCH 2/6] Profile: add items into menu --- src/profiles/Profile.tsx | 46 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index 9015e8b..f92bf95 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -26,10 +26,14 @@ import { Close, Edit, ExpandMore, + Group, MoreVert, OpenInBrowser, + PersonOff, + PlaylistAdd, Send, Share, + Translate, Verified, } from "@suid/icons-material"; import { Title } from "../material/typography"; @@ -90,6 +94,10 @@ const Profile: Component = () => { console.error(err); }); + const isCurrentSessionProfile = () => { + return session().account?.inf?.url === profile()?.url; + }; + const [recentTootFilter, setRecentTootFilter] = createSignal({ pinned: true, boost: false, @@ -261,18 +269,48 @@ const Profile: Component = () => { document.getElementById(menuButId)!.getBoundingClientRect() } > + + + + + Subscribe... + + } + > + + + + + Edit... + + + - + - Edit... + Subscribers + + + + + + Blocklist + + + + + + Translate Name and Bio... - - Mention {profile()?.displayName || ""}... + Mention in... Date: Sun, 3 Nov 2024 18:09:42 +0800 Subject: [PATCH 3/6] anim: adjust speed for grow and shrink --- src/platform/anim.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/platform/anim.ts b/src/platform/anim.ts index 3369e09..dfffc87 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,7 +85,6 @@ export function animateRollOutFromTop( return animation; } - export function animateRollInFromBottom( root: HTMLElement, options?: Omit, @@ -126,8 +125,10 @@ export function animateGrowFromTopRight( const { width, height } = root.getBoundingClientRect(); - const durationX = Math.floor((height / 1600) * 1000); - const durationY = Math.floor((width / 1600) * 1000); + const speed = transitionSpeedForEnter(window.innerHeight); + + const durationX = Math.floor(height / speed); + const durationY = Math.floor(width / speed); // finds the offset for the center frame, // it will stops at the (minDuration / maxDuration)% @@ -137,20 +138,19 @@ export function animateGrowFromTopRight( const centerOffset = minDuration / maxDuration; const keyframes = [ - { transform: "scaleX(0)", opacity: 0, height: "0px", offset: 0 }, + { transform: "scaleX(0.5)", opacity: 0, height: "0px", offset: 0 }, { - transform: `scaleX(${minDuration === durationX ? "1" : centerOffset})`, + transform: `scaleX(${minDuration === durationX ? "1" : centerOffset / 2 + 0.5})`, height: `${(minDuration === durationY ? 1 : centerOffset) * height}px`, offset: centerOffset, - opacity: 1, }, - { transform: "scaleX(1)", height: `${height}px`, offset: 1 }, + { transform: "scaleX(1)", height: `${height}px`, opacity: 1, 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 duration = Math.floor( - Math.max((width / 1600) * 1000, (height / 1600) * 1000), - ); + const speed = transitionSpeedForLeave(window.innerWidth); + + const duration = Math.floor(Math.max(width / speed, height / speed)); const animation = root.animate( { From c69d54e171871d882c46df37d2bd9bf642a212ce Mon Sep 17 00:00:00 2001 From: thislight Date: Sun, 3 Nov 2024 18:23:24 +0800 Subject: [PATCH 4/6] Profile: remove the divier when no pinned toots --- src/profiles/Profile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index f92bf95..4becc5b 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -437,7 +437,7 @@ const Profile: Component = () => { - + 0}> Date: Sun, 3 Nov 2024 18:24:10 +0800 Subject: [PATCH 5/6] TootFilterButton: show the menu in the click pos --- src/material/Menu.tsx | 4 +++- src/profiles/TootFilterButton.tsx | 35 +++++++++++++------------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/material/Menu.tsx b/src/material/Menu.tsx index 04b8d86..a80a423 100644 --- a/src/material/Menu.tsx +++ b/src/material/Menu.tsx @@ -13,10 +13,12 @@ import { animateShrinkToTopRight, } from "../platform/anim"; +type Anchor = Pick + type Props = { open?: boolean; onClose?: JSX.EventHandlerUnion; - anchor: () => DOMRect; + anchor: () => Anchor; }; function px(n?: number) { diff --git a/src/profiles/TootFilterButton.tsx b/src/profiles/TootFilterButton.tsx index 5b9343c..a2a98bc 100644 --- a/src/profiles/TootFilterButton.tsx +++ b/src/profiles/TootFilterButton.tsx @@ -1,15 +1,5 @@ -import { - Button, - MenuItem, - Checkbox, - ListItemText, -} from "@suid/material"; -import { - createMemo, - createSignal, - createUniqueId, - For, -} from "solid-js"; +import { Button, MenuItem, Checkbox, ListItemText } from "@suid/material"; +import { createMemo, createSignal, createUniqueId, For } from "solid-js"; import Menu from "../material/Menu"; import { FilterList, FilterListOff } from "@suid/icons-material"; @@ -68,9 +58,20 @@ function TootFilterButton>(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 ( <> - - - document.getElementById(buttonId)!.getBoundingClientRect() - } - > + anchor}> {(item, idx) => ( <> From 8a435be4c8e675b12fe0dcafcbecb811f86eb94e Mon Sep 17 00:00:00 2001 From: thislight Date: Sun, 3 Nov 2024 18:39:09 +0800 Subject: [PATCH 6/6] BottomSheet: adjust hero animation --- src/material/BottomSheet.tsx | 47 ++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 1258c81..2ebed75 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -11,8 +11,15 @@ import { import "./BottomSheet.css"; import { useHeroSignal } from "../platform/anim"; import material from "./material.module.css"; -import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; -import { animateSlideInFromRight, animateSlideOutToRight } from "../platform/anim"; +import { + ANIM_CURVE_ACELERATION, + ANIM_CURVE_DECELERATION, + ANIM_CURVE_STD, +} from "./theme"; +import { + animateSlideInFromRight, + animateSlideOutToRight, +} from "../platform/anim"; export type BottomSheetProps = { open?: boolean; @@ -90,10 +97,10 @@ const BottomSheet: ParentComponent = (props) => { return; } const onAnimationEnd = () => { - element.classList.remove("animated") - onClose() - } - element.classList.add("animated") + element.classList.remove("animated"); + onClose(); + }; + element.classList.add("animated"); animation = props.bottomUp ? animateSlideInFromBottom(element, true) : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION }); @@ -116,13 +123,15 @@ const BottomSheet: ParentComponent = (props) => { } else if (props.bottomUp) { animateSlideInFromBottom(element); } else if (window.innerWidth <= 560) { - element.classList.add("animated") + 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) + element.classList.remove("animated"); + }; + animation = animateSlideInFromRight(element, { + easing: ANIM_CURVE_DECELERATION, + }); + animation.addEventListener("finish", onAnimationEnd); + animation.addEventListener("cancel", onAnimationEnd); } }; @@ -162,13 +171,19 @@ const BottomSheet: ParentComponent = (props) => { element: HTMLElement, reserve?: boolean, ) => { - const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; + const easing = ANIM_CURVE_STD; element.classList.add("animated"); - const distance = Math.sqrt( + // distance_lt = (|top_start - top_end|^2 + |left_end - left_end|^2)^(-2) + const distancelt = Math.sqrt( Math.pow(Math.abs(startRect.top - endRect.top), 2) + - Math.pow(Math.abs(startRect.left - startRect.top), 2), + Math.pow(Math.abs(startRect.left - endRect.left), 2), ); - const duration = (distance / MOVE_SPEED) * 1000; + 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; animation = element.animate( [ composeAnimationFrame(startRect, { transform: "none" }),