perf(stats-bento): migrate orbit animation to CSS to eliminate re-render loops#450
Conversation
📝 WalkthroughWalkthroughThe orbit item rendering has been refactored from React state with setInterval/trigonometric positioning to pure CSS animations using Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
styles/globals.css (1)
567-615: Consolidate adjacent@layer utilitiesblocks.These two back-to-back utility layers can be merged into one to keep related orbit keyframes together and reduce stylesheet fragmentation.
♻️ Suggested cleanup
`@layer` utilities { `@keyframes` orbit-move { @@ `@keyframes` counter-rotate { @@ } -} - -@layer utilities { `@keyframes` orbit-indexes { @@ } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@styles/globals.css` around lines 567 - 615, Merge the two adjacent `@layer` utilities blocks by moving the `@keyframes` orbit-indexes into the first `@layer` utilities block that already contains `@keyframes` orbit-move and `@keyframes` counter-rotate, then remove the now-empty second `@layer` utilities block so all orbit-related keyframes (orbit-move, counter-rotate, orbit-indexes) live together in a single utilities layer and their order/percent rules remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(main)/_landing/stats-bento.tsx:
- Line 123: Replace the hardcoded purple stop on the Icons.logo element with the
theme accent token: locate the Icons.logo JSX (symbol: Icons.logo) and update
its className to use the accent HSL token instead of from-violet-500 (e.g.
replace the from-violet-500 utility with a token-based utility such as
from-[hsl(var(--accent))] or the project’s equivalent accent class) so the logo
follows the theme accent color.
- Around line 90-106: The component currently uses globally-declared keyframes
for orbit animations; add an inline <style> tag inside the same component that
declares the `@keyframes` used (orbit-move, counter-rotate, orbit-indexes) so the
animations are local to this component, and keep orbitContainerStyle and
itemCorrectionStyle only setting timing values (duration, animationDelay,
animation names) as they already do; ensure the new <style> uses the exact
animation names referenced by orbitContainerStyle and itemCorrectionStyle
(orbit-move, orbit-indexes, counter-rotate) and place the <style> tag adjacent
to the JSX that renders OrbitItem so the keyframes are component-local.
---
Nitpick comments:
In `@styles/globals.css`:
- Around line 567-615: Merge the two adjacent `@layer` utilities blocks by moving
the `@keyframes` orbit-indexes into the first `@layer` utilities block that already
contains `@keyframes` orbit-move and `@keyframes` counter-rotate, then remove the
now-empty second `@layer` utilities block so all orbit-related keyframes
(orbit-move, counter-rotate, orbit-indexes) live together in a single utilities
layer and their order/percent rules remain unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3d487b87-3f20-4927-9084-e547e8bdc412
📒 Files selected for processing (2)
app/(main)/_landing/stats-bento.tsxstyles/globals.css
| const delay = (duration / total) * index * -1; | ||
|
|
||
| useEffect(() => { | ||
| setMounted(true); | ||
| const id = setInterval(() => setAngle((a) => (a + 1) % 360), duration); | ||
| return () => clearInterval(id); | ||
| }, [duration]); | ||
|
|
||
| const rad = (angle * Math.PI) / 180; | ||
| const x = 35 * Math.cos(rad); | ||
| const y = 15 * Math.sin(rad); | ||
| const tilt = (-30 * Math.PI) / 180; | ||
| const xT = x * Math.cos(tilt) - y * Math.sin(tilt); | ||
| const yT = x * Math.sin(tilt) + y * Math.cos(tilt); | ||
| const depth = (Math.sin(rad) + 1) / 2; | ||
|
|
||
| // Round to 2 decimals to prevent hydration mismatch | ||
| const left = Math.round((50 + xT) * 100) / 100; | ||
| const top = Math.round((50 + yT) * 100) / 100; | ||
| const scale = Math.round((0.7 + depth * 0.5) * 100) / 100; | ||
| const op = Math.round((0.35 + depth * 0.65) * 100) / 100; | ||
| const orbitContainerStyle: CSSProperties = { | ||
| top: "50%", | ||
| left: "50%", | ||
| animation: ` | ||
| orbit-move ${duration}s linear infinite, | ||
| orbit-indexes ${duration}s linear infinite | ||
| `, | ||
| animationDelay: `${delay}s, ${delay}s`, | ||
| transformStyle: "preserve-3d", | ||
| }; | ||
| const itemCorrectionStyle: CSSProperties = { | ||
| animation: `counter-rotate ${duration}s linear infinite`, | ||
| animationDelay: `${delay}s`, | ||
| backfaceVisibility: "hidden", | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Keep orbit keyframes component-local per repo animation rule.
OrbitItem now relies on globally declared keyframes (orbit-move, counter-rotate, orbit-indexes). Please move these keyframe definitions into an inline <style> block in this component and keep only per-item timing values in style props.
As per coding guidelines: CSS animations belong in inline <style> blocks inside the component, not in CSS modules or styled-components.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(main)/_landing/stats-bento.tsx around lines 90 - 106, The component
currently uses globally-declared keyframes for orbit animations; add an inline
<style> tag inside the same component that declares the `@keyframes` used
(orbit-move, counter-rotate, orbit-indexes) so the animations are local to this
component, and keep orbitContainerStyle and itemCorrectionStyle only setting
timing values (duration, animationDelay, animation names) as they already do;
ensure the new <style> uses the exact animation names referenced by
orbitContainerStyle and itemCorrectionStyle (orbit-move, orbit-indexes,
counter-rotate) and place the <style> tag adjacent to the JSX that renders
OrbitItem so the keyframes are component-local.
There was a problem hiding this comment.
@hari About the Coderabbitai suggestion to define the keyframes using inline <style> blocks with dangerouslySetInnerHTML in React, it's generally considered an anti-pattern in modern front-end development.
It bypasses CSS minification, makes debugging harder in DevTools, and can cause unnecessary re-parsed styles. Using a dedicated CSS module or keeping it in the global utility layer (as I originally did) is usually the standard approach for better performance and maintainability.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| return ( | ||
| <div className="relative flex h-40 w-full items-center justify-center overflow-hidden rounded-xl border border-border bg-[hsl(var(--surface-alt))] sm:h-48"> | ||
| <Icons.logo className="z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> | ||
| <Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> |
There was a problem hiding this comment.
Use theme accent token for purple/violet logo styling.
The hardcoded from-violet-500 stop should use the accent token to stay theme-consistent.
🎨 Suggested token-based class update
-<Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" />
+<Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-[hsl(var(--accent))] to-[hsl(var(--ring))] p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" />As per coding guidelines: Use theme accent color hsl(var(--accent)) for purple/violet elements.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> | |
| <Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-[hsl(var(--accent))] to-[hsl(var(--ring))] p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(main)/_landing/stats-bento.tsx at line 123, Replace the hardcoded
purple stop on the Icons.logo element with the theme accent token: locate the
Icons.logo JSX (symbol: Icons.logo) and update its className to use the accent
HSL token instead of from-violet-500 (e.g. replace the from-violet-500 utility
with a token-based utility such as from-[hsl(var(--accent))] or the project’s
equivalent accent class) so the logo follows the theme accent color.
|
🚀 Preview deployed to: https://513b2926.animata.pages.dev |
|
@Moamal-2000 let's update the actual component too https://animata.design/docs/list/orbiting-items-3-d re: CSS - yes, I think we will need to update the docs to use a dedicated CSS file so the registry can copy it cleanly without inline CSS. |
|
I agree, @hari. However, since this PR specifically addresses the critical performance issue (re-render loop) on the Home page, I suggest we merge this fix first to stabilize the live site. I can then open a separate PR to refactor the core library component and update the documentation/registry structure as you suggested. This will allow us to handle the CSS architecture change (dedicated files vs inline) more carefully without delaying the performance fix for the Home page. What do you think? |
|
agree @Moamal-2000 🫡 - thanks for the contribution |
This PR fixes issue #449
This PR refactors the
OrbitItemcomponent instats-bento.tsxto improve performance. The previous implementation relied on a JavaScriptsetIntervalanduseStateto drive animations, which triggered high-frequency re-renders (approx. 6 renders every 300ms per item).I have replaced this logic with GPU-accelerated CSS Keyframes, delegating the animation work to the browser's compositor thread. I have also ensured that the new CSS-based animation remains visually identical to the original version, preserving the same orbital path, timing, and easing.
Changes
setInterval,useState, and complex trigonometric math from the React render cycle.orbit-moveandorbit-indexesCSS keyframes to handle 3D-like orbital motion.counter-rotateanimation to maintain icon orientation during the orbit.Performance Impact
Testing
Summary by CodeRabbit
New Features
Style
Accessibility