Cursor Attractor & Gravity
A set of wrapper components for creating physics-based attractors and gravity animations with Matter.js.
Installation
npx shadcn@latest add "https://fancycomponents.dev/r/cursor-attractor-and-gravity.json"
Usage
First, you need to wrap your scene / content with the Gravity
component. Set the attraction point coordinates in the attractorPoint
prop. This point will either attract or repel all the bodies inside the container. Then, in order to transform your regular HTML elements into Matter bodies, you need to wrap them with the MatterBody
component. You need to set each bodies x
and y
position, either as a percentage of your container size, or as a number. You do not need to set the width and height manually, everything else is taken care of by component :). Lastly, set the strength and radius of the cursor attractor, which will also attract or repell all bodies. High-level example:
<Gravity attractorPoint={{ x: "50%", y: "50%" }} attractorStrength={0.0006} cursorStrength={-0.005} cursorFieldRadius={200}>
<MatterBody x="50%" y="50%">
<div>Hello world!</div>
</MatterBody>
<MatterBody x="10%" y="10%">
<div>fancy!</div>
</MatterBody>
</Gravity>
Understanding the component
Please refer to the Gravity documentation, since the component is almost identical. The only difference is that in this component that we don't use a directional gravitational force.Instead, we use attractor force(s), either from a defined static point (optional), and/or from the cursor position, that attract or repel all bodies inside the container. This is achieved by calculating the distance between the attractor point(s) and each body, and applying a force in the opposite direction of the body's velocity. The force is proportional to the distance and inversely proportional to the mass of the body.
Examples
Repel
By setting one of the attractor points' strength to a negative value, you can create a repelling effect. The following demo showcases a negative force from the cursor by applying a negative value to the cursorStrength
prop.
join the community
SVGs
Youy can choose svg
as a bodyType
for your matter bodies. This is particularly useful for creating custom-shaped physics objects that match your SVG graphics.
Here's how it works:
- The component takes your SVG element and extracts the path data
- It converts the path into a series of vertices (points) that outline the shape (with a custom converter using the
svg-path-commander
package) - These vertices are then converted into polygons by matter.js (with the help of the
poly-decomp
package). - The resulting polygons are then used to create Matter.js bodies
fancy components
As you can see in the demo above, SVG bodies can produce varying results. Simple shapes like some of the stars translate well, but some of them are a bit rough.
This variance in quality stems from the challenging process of converting SVG paths to physics bodies. Therefore, there are a few caveats to keep in mind:
-
SVG Requirements:
- Keep them simple. The simpler the SVG, the better the decomposition, and the simulation.
- It's only tested with single-path SVGs, and it probably won't work with nested paths.
- Avoid shapes with holes or complex curves, or shapes that are seem to be too complex to decompose into polygons.
-
Performance Impact:
- Complex SVGs create more detailed physics bodies, which can slow down the simulation
- More vertices mean more calculations
- The initial path-to-vertices conversion can be slow.
If you're not getting the desired results, you have several options:
- Break down complex SVGs into simpler shapes
- Use basic physics bodies (rectangles/circles) with the SVG as a visual overlay
- Fine-tune the vertex sampling with the
sampleLength
prop
You more than likely will need to experiment with different settings to get the desired results. Use the debug
prop to visualize the physics bodies and their vertices, and adjust the sampleLength
prop to control the accuracy of the conversion.
For more details on the decomposition process, refer to the poly-decomp documentation, the Matter.js documentation, and to the SVG path commander documentation.
Props
Prop | Type | Default | Description |
---|---|---|---|
children* | React.ReactNode | - | The content to be displayed |
attractorPoint | { x: number | string; y: number | string } | { x: 0.5, y: 0.5 } | The attractor point coordinates |
attractorStrength | number | 0.001 | The strength of the attractor force |
cursorStrength | number | 0.0005 | The strength of the cursor force |
cursorFieldRadius | number | 100 | The radius of the cursor field |
resetOnResize | boolean | true | Whether to reset the physics world when the window is resized |
addTopWall | boolean | true | Whether to add a wall at the top of the canvas |
autoStart | boolean | true | Whether to automatically start the physics simulation |
className | string | - | Additional CSS classes for styling |