## Integrate the <MagicRings /> component from React Bits
You are helping integrate an open-source React component into an existing application.
### Component: MagicRings
### Variant: JavaScript + CSS
---
### Usage Example
```jsx
import MagicRings from './MagicRings';
<div style={{ width: '600px', height: '400px', position: 'relative' }}>
<MagicRings
color="#3B82F6"
colorTwo="#42fcff"
ringCount={6}
speed={1}
attenuation={10}
lineThickness={2}
baseRadius={0.35}
radiusStep={0.1}
scaleRate={0.1}
opacity={1}
blur={0}
noiseAmount={0.1}
rotation={0}
ringGap={1.5}
fadeIn={0.7}
fadeOut={0.5}
followMouse={false}
mouseInfluence={0.2}
hoverScale={1.2}
parallax={0.05}
clickBurst={false}
/>
</div>
```
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| color | string | "#A855F7" | Hex color for the rings. |
| colorTwo | string | "#6366F1" | Second color — rings interpolate from color to colorTwo. |
| ringCount | number | 6 | Number of concentric rings to draw (1–10). |
| speed | number | 1 | Animation speed multiplier. |
| attenuation | number | 10 | Glow falloff — higher values produce tighter glow. |
| lineThickness | number | 2 | Thickness of each ring line. |
| baseRadius | number | 0.35 | Radius of the innermost ring (normalized). |
| radiusStep | number | 0.1 | Spacing between successive rings. |
| scaleRate | number | 0.1 | How much rings expand over time. |
| opacity | number | 1 | Overall opacity of the effect (0–1). |
| blur | number | 0 | CSS blur in px — creates a bloom/glow effect. |
| noiseAmount | number | 0.1 | Film-grain noise intensity. |
| rotation | number | 0 | Static rotation of the pattern in degrees. |
| ringGap | number | 1.5 | Exponential base for angular cutaway per ring. |
| fadeIn | number | 0.7 | Duration of ring fade-in within cycle. |
| fadeOut | number | 0.5 | Start time of ring fade-out within cycle. |
| followMouse | boolean | false | Rings shift toward the mouse cursor. |
| mouseInfluence | number | 0.2 | Strength of mouse follow (when followMouse is true). |
| hoverScale | number | 1.2 | Scale multiplier on hover. |
| parallax | number | 0.05 | Per-ring depth offset based on mouse position. |
| clickBurst | boolean | false | Click triggers a brightness flash and scale pulse. |
### Full Component Source
```jsx
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './MagicRings.css';
const vertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime, uAttenuation, uLineThickness;
uniform float uBaseRadius, uRadiusStep, uScaleRate;
uniform float uOpacity, uNoiseAmount, uRotation, uRingGap;
uniform float uFadeIn, uFadeOut;
uniform float uMouseInfluence, uHoverAmount, uHoverScale, uParallax, uBurst;
uniform vec2 uResolution, uMouse;
uniform vec3 uColor, uColorTwo;
uniform int uRingCount;
const float HP = 1.5707963;
const float CYCLE = 3.45;
float fade(float t) {
return t < uFadeIn ? smoothstep(0.0, uFadeIn, t) : 1.0 - smoothstep(uFadeOut, CYCLE - 0.2, t);
}
float ring(vec2 p, float ri, float cut, float t0, float px) {
float t = mod(uTime + t0, CYCLE);
float r = ri + t / CYCLE * uScaleRate;
float d = abs(length(p) - r);
float a = atan(abs(p.y), abs(p.x)) / HP;
float th = max(1.0 - a, 0.5) * px * uLineThickness;
float h = (1.0 - smoothstep(th, th * 1.5, d)) + 1.0;
d += pow(cut * a, 3.0) * r;
return h * exp(-uAttenuation * d) * fade(t);
}
void main() {
float px = 1.0 / min(uResolution.x, uResolution.y);
vec2 p = (gl_FragCoord.xy - 0.5 * uResolution.xy) * px;
float cr = cos(uRotation), sr = sin(uRotation);
p = mat2(cr, -sr, sr, cr) * p;
p -= uMouse * uMouseInfluence;
float sc = mix(1.0, uHoverScale, uHoverAmount) + uBurst * 0.3;
p /= sc;
vec3 c = vec3(0.0);
float rcf = max(float(uRingCount) - 1.0, 1.0);
for (int i = 0; i < 10; i++) {
if (i >= uRingCount) break;
float fi = float(i);
vec2 pr = p - fi * uParallax * uMouse;
vec3 rc = mix(uColor, uColorTwo, fi / rcf);
c = mix(c, rc, vec3(ring(pr, uBaseRadius + fi * uRadiusStep, pow(uRingGap, fi), i == 0 ? 0.0 : 2.95 * fi, px)));
}
c *= 1.0 + uBurst * 2.0;
float n = fract(sin(dot(gl_FragCoord.xy + uTime * 100.0, vec2(12.9898, 78.233))) * 43758.5453);
c += (n - 0.5) * uNoiseAmount;
gl_FragColor = vec4(c, max(c.r, max(c.g, c.b)) * uOpacity);
}
`;
export default function MagicRings({
color = '#fc42ff',
colorTwo = '#42fcff',
speed = 1,
ringCount = 6,
attenuation = 10,
lineThickness = 2,
baseRadius = 0.35,
radiusStep = 0.1,
scaleRate = 0.1,
opacity = 1,
blur = 0,
noiseAmount = 0.1,
rotation = 0,
ringGap = 1.5,
fadeIn = 0.7,
fadeOut = 0.5,
followMouse = false,
mouseInfluence = 0.2,
hoverScale = 1.2,
parallax = 0.05,
clickBurst = false,
}) {
const mountRef = useRef(null);
const propsRef = useRef(null);
const mouseRef = useRef([0, 0]);
const smoothMouseRef = useRef([0, 0]);
const hoverAmountRef = useRef(0);
const isHoveredRef = useRef(false);
const burstRef = useRef(0);
propsRef.current = {
color, colorTwo, speed, ringCount, attenuation, lineThickness,
baseRadius, radiusStep, scaleRate, opacity, noiseAmount,
rotation, ringGap, fadeIn, fadeOut, followMouse, mouseInfluence,
hoverScale, parallax, clickBurst,
};
useEffect(() => {
const mount = mountRef.current;
if (!mount) return;
let renderer;
try {
renderer = new THREE.WebGLRenderer({ alpha: true });
} catch {
return;
}
if (!renderer.capabilities.isWebGL2) {
renderer.dispose();
return;
}
renderer.setClearColor(0x000000, 0);
mount.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);
camera.position.z = 1;
const uniforms = {
uTime: { value: 0 },
uAttenuation: { value: 0 },
uResolution: { value: new THREE.Vector2() },
uColor: { value: new THREE.Color() },
uColorTwo: { value: new THREE.Color() },
uLineThickness: { value: 0 },
uBaseRadius: { value: 0 },
uRadiusStep: { value: 0 },
uScaleRate: { value: 0 },
uRingCount: { value: 0 },
uOpacity: { value: 1 },
uNoiseAmount: { value: 0 },
uRotation: { value: 0 },
uRingGap: { value: 1.6 },
uFadeIn: { value: 0.5 },
uFadeOut: { value: 0.75 },
uMouse: { value: new THREE.Vector2() },
uMouseInfluence: { value: 0 },
uHoverAmount: { value: 0 },
uHoverScale: { value: 1 },
uParallax: { value: 0 },
uBurst: { value: 0 },
};
const material = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms, transparent: true });
const quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
scene.add(quad);
const resize = () => {
const w = mount.clientWidth;
const h = mount.clientHeight;
const dpr = Math.min(window.devicePixelRatio, 2);
renderer.setSize(w, h);
renderer.setPixelRatio(dpr);
uniforms.uResolution.value.set(w * dpr, h * dpr);
};
resize();
window.addEventListener('resize', resize);
const ro = new ResizeObserver(resize);
ro.observe(mount);
const onMouseMove = (e) => {
const rect = mount.getBoundingClientRect();
mouseRef.current[0] = (e.clientX - rect.left) / rect.width - 0.5;
mouseRef.current[1] = -((e.clientY - rect.top) / rect.height - 0.5);
};
const onMouseEnter = () => { isHoveredRef.current = true; };
const onMouseLeave = () => {
isHoveredRef.current = false;
mouseRef.current[0] = 0;
mouseRef.current[1] = 0;
};
const onClick = () => { burstRef.current = 1; };
mount.addEventListener('mousemove', onMouseMove);
mount.addEventListener('mouseenter', onMouseEnter);
mount.addEventListener('mouseleave', onMouseLeave);
mount.addEventListener('click', onClick);
let frameId;
const animate = (t) => {
frameId = requestAnimationFrame(animate);
const p = propsRef.current;
smoothMouseRef.current[0] += (mouseRef.current[0] - smoothMouseRef.current[0]) * 0.08;
smoothMouseRef.current[1] += (mouseRef.current[1] - smoothMouseRef.current[1]) * 0.08;
hoverAmountRef.current += ((isHoveredRef.current ? 1 : 0) - hoverAmountRef.current) * 0.08;
burstRef.current *= 0.95;
if (burstRef.current < 0.001) burstRef.current = 0;
uniforms.uTime.value = t * 0.001 * p.speed;
uniforms.uAttenuation.value = p.attenuation;
uniforms.uColor.value.set(p.color);
uniforms.uColorTwo.value.set(p.colorTwo);
uniforms.uLineThickness.value = p.lineThickness;
uniforms.uBaseRadius.value = p.baseRadius;
uniforms.uRadiusStep.value = p.radiusStep;
uniforms.uScaleRate.value = p.scaleRate;
uniforms.uRingCount.value = p.ringCount;
uniforms.uOpacity.value = p.opacity;
uniforms.uNoiseAmount.value = p.noiseAmount;
uniforms.uRotation.value = (p.rotation * Math.PI) / 180;
uniforms.uRingGap.value = p.ringGap;
uniforms.uFadeIn.value = p.fadeIn;
uniforms.uFadeOut.value = p.fadeOut;
uniforms.uMouse.value.set(smoothMouseRef.current[0], smoothMouseRef.current[1]);
uniforms.uMouseInfluence.value = p.followMouse ? p.mouseInfluence : 0;
uniforms.uHoverAmount.value = hoverAmountRef.current;
uniforms.uHoverScale.value = p.hoverScale;
uniforms.uParallax.value = p.parallax;
uniforms.uBurst.value = p.clickBurst ? burstRef.current : 0;
renderer.render(scene, camera);
};
frameId = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(frameId);
window.removeEventListener('resize', resize);
ro.disconnect();
mount.removeEventListener('mousemove', onMouseMove);
mount.removeEventListener('mouseenter', onMouseEnter);
mount.removeEventListener('mouseleave', onMouseLeave);
mount.removeEventListener('click', onClick);
mount.removeChild(renderer.domElement);
renderer.dispose();
material.dispose();
};
}, []);
return <div ref={mountRef} className="magic-rings-container" style={blur > 0 ? { filter: `blur(${blur}px)` } : undefined} />;
}
```
### Component CSS
```css
.magic-rings-container {
width: 100%;
height: 100%;
}
```
### Integration Instructions
1. Install any listed dependencies.
2. Copy the component source into the appropriate directory in the project.
3. Import the CSS file alongside the component.
4. Import and render the component using the usage example above as a starting point.
5. Adjust props as needed for the specific use case — refer to the props table for all available options.