Skip to content

perf(stats-bento): migrate orbit animation to CSS to eliminate re-render loops#450

Merged
hari merged 1 commit intocodse:mainfrom
Moamal-2000:perf/css-orbit-animation
Apr 28, 2026
Merged

perf(stats-bento): migrate orbit animation to CSS to eliminate re-render loops#450
hari merged 1 commit intocodse:mainfrom
Moamal-2000:perf/css-orbit-animation

Conversation

@Moamal-2000
Copy link
Copy Markdown
Contributor

@Moamal-2000 Moamal-2000 commented Apr 28, 2026

This PR fixes issue #449

This PR refactors the OrbitItem component in stats-bento.tsx to improve performance. The previous implementation relied on a JavaScript setInterval and useState to 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

  • Removed setInterval, useState, and complex trigonometric math from the React render cycle.
  • Implemented orbit-move and orbit-indexes CSS keyframes to handle 3D-like orbital motion.
  • Added counter-rotate animation to maintain icon orientation during the orbit.
  • Optimized depth handling (z-index) using synchronized CSS animations.

Performance Impact

  • Re-render count: Reduced from infinite/high-frequency to zero (after initial mount).
  • CPU usage: Significantly lowered as frame-by-frame calculations are now handled by the GPU.
  • Main Thread: Unblocked, leading to smoother interactions in other parts of the UI.

Testing

  • Verified that icons still follow the same orbital path.
  • Verified that items correctly move behind/in front of the central logo.
  • Checked React Profiler to confirm re-render loops are gone.

Summary by CodeRabbit

  • New Features

    • Category marquee pills are now interactive and clickable for direct navigation
  • Style

    • Landing page orbit animation has been optimized with smoother transitions and improved performance
  • Accessibility

    • Improved semantic markup in the hero section

@Moamal-2000 Moamal-2000 temporarily deployed to preview-deployment April 28, 2026 05:30 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

The orbit item rendering has been refactored from React state with setInterval/trigonometric positioning to pure CSS animations using @keyframes definitions. Additionally, the categories marquees now use clickable Link components instead of static divs, with orbit speed adjusted from 35 to 12 and icon positioning updated.

Changes

Cohort / File(s) Summary
Orbit Animation Refactoring
app/(main)/_landing/stats-bento.tsx, styles/globals.css
Migrated orbit item rendering from JS-based state management and setInterval to pure CSS animations. Added three @keyframes rules (orbit-move, counter-rotate, orbit-indexes) and updated component to apply inline CSSProperties with animation delays. Adjusted orbit speed from 35 to 12 and added relative positioning to Icons.logo.
Categories Marquees Enhancement
app/(main)/_landing/stats-bento.tsx
Changed category pills from static <div> elements to clickable Link components with href attributes from each category. Added hover border styling and removed aria-hidden="true" from wrapper container.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • feat: update landing page #424: Both PRs modify the orbit rendering in stats-bento.tsx—the referenced PR introduced the interval/trig-based animation logic that this PR refactors into CSS animations.

Suggested reviewers

  • sudhashrestha

Poem

🐰 Orbits spinning, smooth and fast,
CSS now holds the cast,
No more React state to track,
Animation brings performance back!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: migrating orbit animation from JavaScript (re-render loops) to CSS for performance optimization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
styles/globals.css (1)

567-615: Consolidate adjacent @layer utilities blocks.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2de8170 and 0ac6584.

📒 Files selected for processing (2)
  • app/(main)/_landing/stats-bento.tsx
  • styles/globals.css

Comment on lines +90 to +106
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",
};
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
<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.

@github-actions
Copy link
Copy Markdown

🚀 Preview deployed to: https://513b2926.animata.pages.dev

@hari
Copy link
Copy Markdown
Contributor

hari commented Apr 28, 2026

@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.

@Moamal-2000
Copy link
Copy Markdown
Contributor Author

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?

@hari
Copy link
Copy Markdown
Contributor

hari commented Apr 28, 2026

agree @Moamal-2000 🫡 - thanks for the contribution

@hari hari merged commit a5bebcd into codse:main Apr 28, 2026
3 checks passed
@Moamal-2000 Moamal-2000 deleted the perf/css-orbit-animation branch April 28, 2026 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants