Skip to content

feat(floating-3d-particles): add canvas-based 3d particle background - #1002

Open
Max-Samson wants to merge 3 commits into
magicuidesign:mainfrom
Max-Samson:feat/floating-3d-particles
Open

feat(floating-3d-particles): add canvas-based 3d particle background#1002
Max-Samson wants to merge 3 commits into
magicuidesign:mainfrom
Max-Samson:feat/floating-3d-particles

Conversation

@Max-Samson

@Max-Samson Max-Samson commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Add a pseudo-3D floating particle background component, rebuilt from the classic GitMind hero effect as a reusable, configurable UI component.

  • Perspective-projected particle field with continuous rotation, vertical floating and depth-based scaling
  • 22 props: particle counts, color, size/opacity ranges, rotation/float speed, perspective (fov, distance, depth), mouse interaction, DPI scaling, pause-when-hidden and reduced-motion support
  • Mouse rotation listens on window so the canvas stays pointer-events-none and never blocks clicks; disable with mouseInteraction={false}
  • Responsive particle count based on the viewport breakpoint
  • Default demo and hero-content demo, docs page and registry artifacts

Description

Changes

Motivation

Breaking Changes

Screenshots

Device / Browser / Viewport:

Before After

Checklist

  • pnpm check passes locally
  • pnpm build passes locally
  • pnpm build:registry was run and the generated files are committed (if you changed registry/ or config/site.ts — CI verifies this on every PR)
  • PR title follows Conventional Commits (e.g., feat(marquee): add reverse prop)

Add a pseudo-3D floating particle background component, rebuilt from the classic GitMind hero effect as a reusable, configurable UI component.

- Perspective-projected particle field with continuous rotation, vertical floating and depth-based scaling
- 22 props: particle counts, color, size/opacity ranges, rotation/float speed, perspective (fov, distance, depth), mouse interaction, DPI scaling, pause-when-hidden and reduced-motion support
- Mouse rotation listens on window so the canvas stays pointer-events-none and never blocks clicks; disable with mouseInteraction={false}
- Responsive particle count based on the viewport breakpoint
- Default demo and hero-content demo, docs page and registry artifacts
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

@Max-Samson is attempting to deploy a commit to the product-studio Team on Vercel.

A member of the Team first needs to authorize it.

@Yeom-JinHo
Yeom-JinHo self-requested a review August 17, 2026 12:45
@Max-Samson

Copy link
Copy Markdown
Contributor Author

Hi @Yeom-JinHo ! Just wanted to check if there’s anything else needed from my side for this PR. I noticed the Vercel preview may still require team authorization. Happy to make any changes or updates if needed. Thanks!

@Yeom-JinHo Yeom-JinHo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution — this is a genuinely lovely component. The perspective projection reads beautifully behind hero content, and shipping it with zero dependencies plus aria-hidden, pointer-events-none and a reduced-motion path is more care than most background components get.

That said, I'm hesitant to merge as-is. I ran it locally against apps/www and hit following:

  1. prefers-reduced-motion renders an empty canvas. The reduced-motion branch in render() draws one static frame and returns without re-queueing rAF, so the loop dies. resize() then reassigns canvas.width, which clears it — and ResizeObserver always fires an initial callback after the first rAF, so the canvas ends up blank from the very first frame. Verified: 0 painted pixels vs ~37k in the normal path. Same on every resize, and it never recovers if the user turns reduced motion off.

  2. icon-cloud registry metadata changed, unrelated to this PR. registry-ui.ts adds dependencies: ["lucide-react"] and registryDependencies: ["button"] to icon-cloud, but that component imports neither.

  3. 22 props is the highest in the registry, and prop removal becomes breaking after release. background/zIndex are already reachable via style; smoothMouse folds into mouseSmoothing={0}; highDpi into maxDpr={1}. Also worth clamping fov/perspectiveDistance/depthRange — some combinations make the projection divide by zero.

Minor: the two demo descriptions in registry-examples.ts look swapped, and mobileBreakpoint's JSDoc says "container width" where the code reads ``window.innerWidth`.

Could you take a pass at these and re-request review when you're ready?

@Yeom-JinHo

Copy link
Copy Markdown
Member

Visual notes, separate from the review above:

  • Contrast. #8B5CF6 at 0.1–0.5 is washed out on light and nearly invisible on dark — projectedScale multiplies the alpha down a second time in drawParticle. particles-demo.tsx flips the color with useTheme; worth doing the same here.
  • Demo order. The hero demo sells the component far better than the plain one — I'd swap them.
  • Narrow screens. radius scales off container width only, so particles bunch into a vertical cluster on mobile — max(width, height) would spread them out.

@Max-Samson

Copy link
Copy Markdown
Contributor Author

Thanks for the contribution — this is a genuinely lovely component. The perspective projection reads beautifully behind hero content, and shipping it with zero dependencies plus aria-hidden, pointer-events-none and a reduced-motion path is more care than most background components get.

That said, I'm hesitant to merge as-is. I ran it locally against apps/www and hit following:

  1. prefers-reduced-motion renders an empty canvas. The reduced-motion branch in render() draws one static frame and returns without re-queueing rAF, so the loop dies. resize() then reassigns canvas.width, which clears it — and ResizeObserver always fires an initial callback after the first rAF, so the canvas ends up blank from the very first frame. Verified: 0 painted pixels vs ~37k in the normal path. Same on every resize, and it never recovers if the user turns reduced motion off.
  2. icon-cloud registry metadata changed, unrelated to this PR. registry-ui.ts adds dependencies: ["lucide-react"] and registryDependencies: ["button"] to icon-cloud, but that component imports neither.
  3. 22 props is the highest in the registry, and prop removal becomes breaking after release. background/zIndex are already reachable via style; smoothMouse folds into mouseSmoothing={0}; highDpi into maxDpr={1}. Also worth clamping fov/perspectiveDistance/depthRange — some combinations make the projection divide by zero.

Minor: the two demo descriptions in registry-examples.ts look swapped, and mobileBreakpoint's JSDoc says "container width" where the code reads ``window.innerWidth`.

Could you take a pass at these and re-request review when you're ready?

Hi @Yeom-JinHo , thank you so much for the thorough review and for taking the time to test the component locally. I really appreciate the detailed feedback.

I’ve gone through your comments and tested the issues you pointed out on my side. I agree with your suggestions, and I’ll take a pass at the component and make the necessary adjustments, including the reduced-motion behavior, registry metadata, prop surface, projection safety, visual contrast, mobile behavior, and the demo/documentation details.

I’ll make sure to test the changes locally and verify everything before pushing the update. Once the changes are ready, I’ll update the PR and re-request your review.

Thanks again for the thoughtful review — it’s really helpful for improving the component and making it a better fit for Magic UI.

- fix(a11y): keep rAF loop running in reduced-motion mode
- fix(registry): remove unrelated deps from icon-cloud metadata
- refactor(props): streamline prop surface to 6 core visual props
- fix(projection): clamp parameters and prevent divide-by-zero
- fix(render): eliminate secondary alpha decay for proper contrast
- fix(responsive): scale orbit radius with max(width, height)
- perf: remove pointer listeners for pure auto-rotating 3D field
- docs: update MDX props table, usage guidelines, and demos
@Max-Samson
Max-Samson requested a review from Yeom-JinHo August 23, 2026 14:09
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