reactbits-mcp-server
Version:
MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements
305 lines (260 loc) • 7.82 kB
JSX
import { useRef, useEffect, useState } from 'react';
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl';
const vertex = /* glsl */ `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = /* glsl */ `
precision mediump float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 uOffset;
uniform float uRotation;
uniform float focalLength;
uniform float speed1;
uniform float speed2;
uniform float dir2;
uniform float bend1;
uniform float bend2;
uniform float bendAdj1;
uniform float bendAdj2;
uniform float uOpacity;
const float lt = 0.05;
const float pi = 3.141592653589793;
const float pi2 = pi * 2.0;
const float pi_2 = pi * 0.5;
#define MAX_STEPS 15
#define A(v) mat2(cos(m.v + radians(vec4(0.0,-90.0,90.0,0.0))))
void mainImage(out vec4 C, in vec2 U) {
float t = iTime * pi;
float s = 1.0;
float d = 0.0;
vec2 R = iResolution;
vec2 m = vec2(0.0);
vec3 o = vec3(0.0, 0.0, -7.0);
vec3 u = normalize(vec3((U - 0.5 * R) / R.y, focalLength));
vec3 k = vec3(0.0);
vec3 p;
mat2 v = A(y), h = A(x);
float t1 = t * 0.7;
float t2 = t * 0.9;
float tSpeed1 = t * speed1;
float tSpeed2 = t * speed2 * dir2;
for (int step = 0; step < MAX_STEPS; ++step) {
p = o + u * d;
p.yz *= v;
p.xz *= h;
p.x -= 15.0;
float px = p.x;
float wob1 = bend1 + bendAdj1 + sin(t1 + px * 0.8) * 0.1;
float wob2 = bend2 + bendAdj2 + cos(t2 + px * 1.1) * 0.1;
vec2 baseOffset = vec2(px, px + pi_2);
vec2 sinOffset = sin(baseOffset + tSpeed1) * wob1;
vec2 cosOffset = cos(baseOffset + tSpeed2) * wob2;
float wSin = length(p.yz - sinOffset) - lt;
float wCos = length(p.yz - cosOffset) - lt;
k.x = max(px + lt, wSin);
k.y = max(px + lt, wCos);
s = min(s, min(k.x, k.y));
if (s < 0.001 || d > 400.0) break;
d += s * 0.7;
}
vec3 c = max(cos(d * pi2) - s * sqrt(d) - k, 0.0);
c.gb += 0.1;
if (max(c.r, max(c.g, c.b)) < 0.15) discard;
C = vec4(c * 0.4 + c.brg * 0.6 + c * c, uOpacity);
}
void main() {
vec2 coord = gl_FragCoord.xy + uOffset;
coord -= 0.5 * iResolution;
float c = cos(uRotation), s = sin(uRotation);
coord = mat2(c, -s, s, c) * coord;
coord += 0.5 * iResolution;
vec4 color;
mainImage(color, coord);
gl_FragColor = color;
}
`;
export default function PlasmaWaveV2({
xOffset = 0,
yOffset = 0,
rotationDeg = 0,
focalLength = 0.8,
speed1 = 0.1,
speed2 = 0.1,
dir2 = 1.0,
bend1 = 0.9,
bend2 = 0.6,
fadeInDuration = 2000
}) {
const [isMobile, setIsMobile] = useState(false);
const [isVisible, setIsVisible] = useState(true);
const containerRef = useRef(null);
const uniformOffset = useRef(new Float32Array([xOffset, yOffset]));
const uniformResolution = useRef(new Float32Array([1, 1]));
const rendererRef = useRef(null);
const fadeStartTime = useRef(null);
const lastTimeRef = useRef(0);
const pausedTimeRef = useRef(0);
const propsRef = useRef({
xOffset, yOffset, rotationDeg, focalLength,
speed1, speed2, dir2, bend1, bend2, fadeInDuration,
});
propsRef.current = {
xOffset, yOffset, rotationDeg, focalLength,
speed1, speed2, dir2, bend1, bend2, fadeInDuration,
};
useEffect(() => {
const checkIsMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
return () => window.removeEventListener('resize', checkIsMobile);
}, []);
useEffect(() => {
if (!containerRef.current || isMobile) return;
const observer = new IntersectionObserver(
([entry]) => {
setIsVisible(entry.isIntersecting);
},
{
rootMargin: '50px',
threshold: 0.1,
}
);
observer.observe(containerRef.current);
return () => observer.disconnect();
}, [isMobile]);
useEffect(() => {
if (isMobile) {
return;
}
const renderer = new Renderer({
alpha: true,
dpr: Math.min(window.devicePixelRatio, 1),
antialias: false,
depth: false,
stencil: false,
powerPreference: 'high-performance',
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
containerRef.current.appendChild(gl.canvas);
const camera = new Camera(gl);
const scene = new Transform();
const geometry = new Geometry(gl, {
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) },
});
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
iTime: { value: 0 },
iResolution: { value: uniformResolution.current },
uOffset: { value: uniformOffset.current },
uRotation: { value: 0 },
focalLength: { value: focalLength },
speed1: { value: speed1 },
speed2: { value: speed2 },
dir2: { value: dir2 },
bend1: { value: bend1 },
bend2: { value: bend2 },
bendAdj1: { value: 0 },
bendAdj2: { value: 0 },
uOpacity: { value: 0 },
},
});
new Mesh(gl, { geometry, program }).setParent(scene);
const resize = () => {
const { width, height } =
containerRef.current?.getBoundingClientRect() || { width: 0, height: 0 };
renderer.setSize(width, height);
uniformResolution.current[0] = width * renderer.dpr;
uniformResolution.current[1] = height * renderer.dpr;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(containerRef.current);
let rafId;
const loop = now => {
const {
xOffset: xOff,
yOffset: yOff,
rotationDeg: rot,
focalLength: fLen,
fadeInDuration: fadeDur,
} = propsRef.current;
if (isVisible) {
if (lastTimeRef.current === 0) {
lastTimeRef.current = now - pausedTimeRef.current;
}
const t = (now - lastTimeRef.current) * 0.001;
if (fadeStartTime.current === null && t > 0.1) {
fadeStartTime.current = now;
}
let opacity = 0;
if (fadeStartTime.current !== null) {
const fadeElapsed = now - fadeStartTime.current;
opacity = Math.min(fadeElapsed / fadeDur, 1);
opacity = 1 - Math.pow(1 - opacity, 3);
}
uniformOffset.current[0] = xOff;
uniformOffset.current[1] = yOff;
program.uniforms.iTime.value = t;
program.uniforms.uRotation.value = rot * Math.PI / 180;
program.uniforms.focalLength.value = fLen;
program.uniforms.uOpacity.value = opacity;
renderer.render({ scene, camera });
} else {
if (lastTimeRef.current !== 0) {
pausedTimeRef.current = now - lastTimeRef.current;
lastTimeRef.current = 0;
}
}
rafId = requestAnimationFrame(loop);
};
rafId = requestAnimationFrame(loop);
return () => {
cancelAnimationFrame(rafId);
ro.disconnect();
renderer.gl.canvas.remove();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMobile, isVisible]);
if (isMobile) {
return null;
}
return (
<div
ref={containerRef}
style={{
position: 'absolute',
inset: 0,
overflow: 'hidden',
width: '100vw',
height: '100vh'
}}
>
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 200,
background: 'linear-gradient(to top, #060010, transparent)',
pointerEvents: 'none',
zIndex: 1,
}}
/>
</div>
);
}