feat(ui): use motion for page, tab, switch and press gestures animation#2406
feat(ui): use motion for page, tab, switch and press gestures animation#2406bajrangCoder wants to merge 13 commits into
Conversation
- Replaced choppy CSS transitions on page open/close with pure GPU-accelerated fade animations using motion.dev. - Implemented a sliding active tab indicator using transform scaleX and translate3d, tracked dynamically via MutationObserver to support programmatic changes. - Refactored switch toggle check handle to use a real DOM element with snappy spring physics. - Added spring press scaling gestures (scale: 0.985) on list items, back arrows, and side buttons.
Greptile SummaryThis PR introduces the
Confidence Score: 4/5Safe to merge for most users, but the no-animation path is broken — users who disabled animations in settings will still see all Motion-powered effects. The core animation logic is well-structured and the tab lifecycle handling is a clear improvement. The blocking issue is that WAAPI (used by Motion and the raw element.animate() calls in editorFileTab.js) is not suppressed by body.no-animation * { animation: none !important; transition: none !important; } — these CSS rules only affect CSS transitions and @Keyframes. Every new animation in this PR will fire regardless of the user's animation preference, and the JSDoc in animateTabReorder incorrectly claims the opposite. All files that call animate(), press(), or hover() from Motion, and editorFileTab.js where raw WAAPI element.animate() is used — specifically wcPage.js, checkbox/index.js, settingsPage.js, sideButton/index.js, tabView.js, and editorFileTab.js. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant WCPage
participant Motion
participant WAAPI
User->>WCPage: navigate to page
WCPage->>Motion: "animate(el, {opacity:1}, {duration:0.14})"
Motion->>WAAPI: element.animate([...])
WAAPI-->>WCPage: animation plays (ignores body.no-animation CSS)
User->>WCPage: press back button
WCPage->>Motion: press(leadBtn, ...)
Motion->>WAAPI: "element.animate([{scale:0.85}])"
WAAPI-->>WCPage: spring scale plays
User->>WCPage: hide page
WCPage->>Motion: "animate(el, {opacity:0}, {duration:0.12})"
Motion->>WAAPI: element.animate([...])
WAAPI-->>WCPage: fade out, then remove()
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant WCPage
participant Motion
participant WAAPI
User->>WCPage: navigate to page
WCPage->>Motion: "animate(el, {opacity:1}, {duration:0.14})"
Motion->>WAAPI: element.animate([...])
WAAPI-->>WCPage: animation plays (ignores body.no-animation CSS)
User->>WCPage: press back button
WCPage->>Motion: press(leadBtn, ...)
Motion->>WAAPI: "element.animate([{scale:0.85}])"
WAAPI-->>WCPage: spring scale plays
User->>WCPage: hide page
WCPage->>Motion: "animate(el, {opacity:0}, {duration:0.12})"
Motion->>WAAPI: element.animate([...])
WAAPI-->>WCPage: fade out, then remove()
Reviews (4): Last reviewed commit: "format" | Re-trigger Greptile |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
| connectedCallback() { | ||
| this.classList.remove("hide"); | ||
| const isPrimary = this.classList.contains("primary"); | ||
| const isNoTransition = this.classList.contains("no-transition"); | ||
|
|
||
| if (!isPrimary) { | ||
| this.style.opacity = "0"; | ||
| animate( | ||
| this, | ||
| { | ||
| opacity: 1, | ||
| }, | ||
| { | ||
| duration: isNoTransition ? 0.08 : 0.14, | ||
| ease: "easeOut", | ||
| }, | ||
| ).then(() => { | ||
| this.style.opacity = ""; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Motion animations bypass
body.no-animation CSS rule
main.scss disables all animations for users who opt out via body.no-animation * { animation: none !important; transition: none !important; }. That rule only suppresses CSS transitions and @keyframes — it has no effect on WAAPI (element.animate()), which is what Motion uses internally. As a result, every new animation added in this PR (page fade-in/fade-out here, spring tab indicator in tabView.js, switch toggle in checkbox/index.js, press/hover effects in settingsPage.js and sideButton/index.js) will still fire even when the user has explicitly disabled animations in Acode settings.
A minimal fix is to skip the animation when body.no-animation is active — e.g. guard each animate(…) call with if (!document.body.classList.contains('no-animation')), or pass duration: 0 in that case. The same guard is needed wherever Motion's press() and hover() utilities are called, since their callbacks invoke animate().
No description provided.