All PromptsAll Prompts
animationbackground
Prismatic Burst Background
WebGL-эффект призматического взрыва для фона. Настраиваемая анимация, лучи, палитры. Оптимизирован для 60fps.
by Zhou JasonLive Preview
Prompt
# Prismatic Burst Background
You are given a task to integrate an existing React component in the codebase
~~~/README.md
# PrismaticBurst
A high-performance WebGL prismatic burst background effect built with [OGL](https://github.com/o-gl/ogl). It features customizable ray patterns, 3D animations, and dynamic color gradients.
## Dependencies
- `ogl`: ^0.0.116
- `react`: ^18.x
- `react-dom`: ^18.x
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `intensity` | `number` | `2` | Overall brightness/intensity of the rays. |
| `speed` | `number` | `0.5` | Speed of the animation loops. |
| `animationType` | `'rotate' \| 'rotate3d' \| 'hover'` | `'rotate3d'` | The logic for how the rays move/interact. |
| `colors` | `string[]` | `undefined` | Array of hex color strings to create the prismatic gradient. |
| `distort` | `number` | `0` | Amount of wave distortion applied to rays. |
| `paused` | `boolean` | `false` | Whether to pause the time-based animation. |
| `offset` | `Offset` | `{ x: 0, y: 0 }` | X/Y offset for the center of the burst. |
| `hoverDampness` | `number` | `0.25` | Smoothness of mouse follow (0-1). |
| `rayCount` | `number` | `24` | Number of distinct rays to render. |
| `mixBlendMode` | `CSSProperties['mixBlendMode'] \| 'none'` | `'lighten'` | Canvas CSS blend mode. |
| `className` | `string` | `""` | Additional CSS classes for the container. |
## Usage
```tsx
import { PrismaticBurst } from '@/sd-components/2608b43f-d5c5-4949-bb5e-0a90133ae4c6';
function MyBackground() {
return (
<div style={{ width: '100%', height: '600px' }}>
<PrismaticBurst
animationType="rotate3d"
colors={['#ff007a', '#4d3dff', '#ffffff']}
rayCount={32}
speed={0.5}
/>
</div>
);
}
```
~~~
~~~/src/App.tsx
import React from 'react';
import { PrismaticBurst } from './Component';
/**
* App component showcasing the PrismaticBurst background effect.
* The layout is kept minimal to focus entirely on the motion as per the style guide.
*/
export default function App() {
return (
<div className="w-full h-full min-h-screen bg-[#1A1A1B] flex items-center justify-center p-20">
{/*
A clean, borderless container with a soft shadow to give it a floating premium feel.
Ample whitespace (p-20) around the element keeps the focus on the motion.
*/}
<div className="relative w-full aspect-video max-w-5xl rounded-[40px] overflow-hidden shadow-[0_40px_100px_rgba(0,0,0,0.5)] border border-white/5">
<PrismaticBurst
animationType="rotate3d"
intensity={1.8}
speed={0.4}
distort={1.2}
paused={false}
offset={{ x: 0, y: 0 }}
hoverDampness={0.25}
rayCount={32}
mixBlendMode="lighten"
colors={['#ff007a', '#4d3dff', '#ffffff', '#00d2ff']}
/>
{/* Subtle overlay for context as per minimalist showcase style */}
<div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
<h1 className="text-white text-4xl font-medium tracking-tight opacity-80 font-sans">
Prismatic Burst
</h1>
<p className="text-white/40 mt-2 font-normal font-sans">
High-Performance WebGL Fluid Motion
</p>
</div>
</div>
</div>
);
}
~~~
~~~/package.json
{
"name": "prismatic-burst-showcase",
"description": "WebGL prismatic burst background effect",
"dependencies": {
"ogl": "^0.0.116",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
~~~
~~~/src/Component.tsx
/**
* PrismaticBurst Component
* A high-performance WebGL background effect using OGL.
* Features customizable rays, colors, and 3D rotation animations.
*/
import React, { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';
export type Offset = { x?: number | string; y?: number | string };
export type AnimationType = 'rotate' | 'rotate3d' | 'hover';
export interface PrismaticBurstProps {
/** Intensity of the light rays */
intensity?: number;
/** Speed of the animation */
speed?: number;
/** Type of animation logic */
animationType?: AnimationType;
/** Array of hex colors for the gradient */
colors?: string[];
/** Distort amount for the rays */
distort?: number;
/** Pause the animation */
paused?: boolean;
/** Offset of the center point */
offset?: Offset;
/** Smoothness of the hover reaction (0-1) */
hoverDampness?: number;
/** Number of discrete rays to render */
rayCount?: number;
/** CSS mix-blend-mode for the canvas */
mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none';
/** Optional class name for the container */
className?: string;
}
const vertexShader = `#version 300 es
in vec2 position;
in vec2 uv;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `#version 300 es
precision highp float;
precision highp int;
out vec4 fragColor;
uniform vec2 uResolution;
uniform float uTime;
uniform float uIntensity;
uniform float uSpeed;
uniform int uAnimType;
uniform vec2 uMouse;
uniform int uColorCount;
uniform float uDistort;
uniform vec2 uOffset;
uniform sampler2D uGradient;
uniform float uNoiseAmount;
uniform int uRayCount;
float hash21(vec2 p){
p = floor(p);
float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));
return fract(f);
}
mat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }
float layeredNoise(vec2 fragPx){
vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);
vec2 q = rot30() * p;
float n = 0.0;
n += 0.40 * hash21(q);
n += 0.25 * hash21(q * 2.0 + 17.0);
n += 0.20 * hash21(q * 4.0 + 47.0);
n += 0.10 * hash21(q * 8.0 + 113.0);
n += 0.05 * hash21(q * 16.0 + 191.0);
return n;
}
vec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){
float focal = res.y * max(dist, 1e-3);
return normalize(vec3(2.0 * (frag - offset) - res, focal));
}
float edgeFade(vec2 frag, vec2 res, vec2 offset){
vec2 toC = frag - 0.5 * res - offset;
float r = length(toC) / (0.5 * min(res.x, res.y));
float x = clamp(r, 0.0, 1.0);
float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
float s = q * 0.5;
s = pow(s, 1.5);
float tail = 1.0 - pow(1.0 - s, 2.0);
s = mix(s, tail, 0.2);
float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;
return clamp(s + dn, 0.0, 1.0);
}
mat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }
mat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }
mat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }
vec3 sampleGradient(float t){
t = clamp(t, 0.0, 1.0);
return texture(uGradient, vec2(t, 0.5)).rgb;
}
vec2 rot2(vec2 v, float a){
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c) * v;
}
float bendAngle(vec3 q, float t){
float a = 0.8 * sin(q.x * 0.55 + t * 0.6)
+ 0.7 * sin(q.y * 0.50 - t * 0.5)
+ 0.6 * sin(q.z * 0.60 + t * 0.7);
return a;
}
void main(){
vec2 frag = gl_FragCoord.xy;
float t = uTime * uSpeed;
float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);
vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);
float marchT = 0.0;
vec3 col = vec3(0.0);
float n = layeredNoise(frag);
vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));
mat2 M2 = mat2(c.x, c.y, c.z, c.w);
float amp = clamp(uDistort, 0.0, 50.0) * 0.15;
mat3 rot3dMat = mat3(1.0);
if(uAnimType == 1){
vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);
rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);
}
mat3 hoverMat = mat3(1.0);
if(uAnimType == 2){
vec2 m = uMouse * 2.0 - 1.0;
vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);
hoverMat = rotY(ang.y) * rotX(ang.x);
}
for (int i = 0; i < 44; ++i) {
vec3 P = marchT * dir;
P.z -= 2.0;
float rad = length(P);
vec3 Pl = P * (10.0 / max(rad, 1e-6));
if(uAnimType == 0){
Pl.xz *= M2;
} else if(uAnimType == 1){
Pl = rot3dMat * Pl;
} else {
Pl = hoverMat * Pl;
}
float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;
float grow = smoothstep(0.35, 3.0, marchT);
float a1 = amp * grow * bendAngle(Pl * 0.6, t);
float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);
vec3 Pb = Pl;
Pb.xz = rot2(Pb.xz, a1);
Pb.xy = rot2(Pb.xy, a2);
float rayPattern = smoothstep(
0.5, 0.7,
sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *
sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))
);
if (uRayCount > 0) {
float ang = atan(Pb.y, Pb.x);
float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);
comb = pow(comb, 3.0);
rayPattern *= smoothstep(0.15, 0.95, comb);
}
vec3 spectralDefault = 1.0 + vec3(
cos(marchT * 3.0 + 0.0),
cos(marchT * 3.0 + 1.0),
cos(marchT * 3.0 + 2.0)
);
float saw = fract(marchT * 0.25);
float tRay = saw * saw * (3.0 - 2.0 * saw);
vec3 userGradient = 2.0 * sampleGradient(tRay);
vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;
vec3 base = (0.05 / (0.4 + stepLen))
* smoothstep(5.0, 0.0, rad)
* spectral;
col += base * rayPattern;
marchT += stepLen;
}
col *= edgeFade(frag, uResolution, uOffset);
col *= uIntensity;
fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}
`;
const hexToRgb01 = (hex: string): [number, number, number] => {
let h = hex.trim();
if (h.startsWith('#')) h = h.slice(1);
if (h.length === 3) {
const r = h[0], g = h[1], b = h[2];
h = r + r + g + g + b + b;
}
const intVal = parseInt(h, 16);
if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];
const r = ((intVal >> 16) & 255) / 255;
const g = ((intVal >> 8) & 255) / 255;
const b = (intVal & 255) / 255;
return [r, g, b];
};
const toPx = (v: number | string | undefined): number => {
if (v == null) return 0;
if (typeof v === 'number') return v;
const s = String(v).trim();
const num = parseFloat(s.replace('px', ''));
return isNaN(num) ? 0 : num;
};
export function PrismaticBurst({
intensity = 2,
speed = 0.5,
animationType = 'rotate3d',
colors,
distort = 0,
paused = false,
offset = { x: 0, y: 0 },
hoverDampness = 0.25,
rayCount = 24,
mixBlendMode = 'lighten',
className = ""
}: PrismaticBurstProps) {
const containerRef = useRef<HTMLDivElement>(null);
const programRef = useRef<Program | null>(null);
const rendererRef = useRef<Renderer | null>(null);
const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]);
const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]);
const pausedRef = useRef<boolean>(paused);
const gradTexRef = useRef<Texture | null>(null);
const hoverDampRef = useRef<number>(hoverDampness);
const isVisibleRef = useRef<boolean>(true);
const meshRef = useRef<Mesh | null>(null);
useEffect(() => {
pausedRef.current = paused;
}, [paused]);
useEffect(() => {
hoverDampRef.current = hoverDampness;
}, [hoverDampness]);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const renderer = new Renderer({ dpr, alpha: false, antialias: false });
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.position = 'absolute';
gl.canvas.style.inset = '0';
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';
container.appendChild(gl.canvas);
const white = new Uint8Array([255, 255, 255, 255]);
const gradientTex = new Texture(gl, {
image: white,
width: 1,
height: 1,
generateMipmaps: false,
flipY: false
});
gradientTex.minFilter = gl.LINEAR;
gradientTex.magFilter = gl.LINEAR;
gradientTex.wrapS = gl.CLAMP_TO_EDGE;
gradientTex.wrapT = gl.CLAMP_TO_EDGE;
gradTexRef.current = gradientTex;
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uResolution: { value: [1, 1] as [number, number] },
uTime: { value: 0 },
uIntensity: { value: intensity },
uSpeed: { value: speed },
uAnimType: { value: 0 },
uMouse: { value: [0.5, 0.5] as [number, number] },
uColorCount: { value: 0 },
uDistort: { value: distort },
uOffset: { value: [0, 0] as [number, number] },
uGradient: { value: gradientTex },
uNoiseAmount: { value: 0.8 },
uRayCount: { value: rayCount }
}
});
programRef.current = program;
const triangle = new Triangle(gl);
const mesh = new Mesh(gl, { geometry: triangle, program });
meshRef.current = mesh;
const resize = () => {
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
renderer.setSize(w, h);
program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];
};
let ro: ResizeObserver | null = null;
if ('ResizeObserver' in window) {
ro = new ResizeObserver(resize);
ro.observe(container);
} else {
window.addEventListener('resize', resize);
}
resize();
const onPointer = (e: PointerEvent) => {
const rect = container.getBoundingClientRect();
const x = (e.clientX - rect.left) / Math.max(rect.width, 1);
const y = (e.clientY - rect.top) / Math.max(rect.height, 1);
mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];
};
container.addEventListener('pointermove', onPointer, { passive: true });
let io: IntersectionObserver | null = null;
if ('IntersectionObserver' in window) {
io = new IntersectionObserver(
entries => {
if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;
},
{ root: null, threshold: 0.01 }
);
io.observe(container);
}
let raf = 0;
let last = performance.now();
let accumTime = 0;
const update = (now: number) => {
const dt = Math.max(0, now - last) * 0.001;
last = now;
const visible = isVisibleRef.current && !document.hidden;
if (!pausedRef.current) accumTime += dt;
if (!visible) {
raf = requestAnimationFrame(update);
return;
}
const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;
const alpha = 1 - Math.exp(-dt / tau);
const tgt = mouseTargetRef.current;
const sm = mouseSmoothRef.current;
sm[0] += (tgt[0] - sm[0]) * alpha;
sm[1] += (tgt[1] - sm[1]) * alpha;
program.uniforms.uMouse.value = sm as [number, number];
program.uniforms.uTime.value = accumTime;
renderer.render({ scene: meshRef.current! });
raf = requestAnimationFrame(update);
};
raf = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(raf);
container.removeEventListener('pointermove', onPointer);
ro?.disconnect();
if (!ro) window.removeEventListener('resize', resize);
io?.disconnect();
try {
if (container && gl.canvas) container.removeChild(gl.canvas);
} catch (e) {
void e;
}
meshRef.current = null;
programRef.current = null;
try {
const glCtx = rendererRef.current?.gl;
if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);
} catch (e) {
void e;
}
rendererRef.current = null;
gradTexRef.current = null;
};
}, []);
useEffect(() => {
const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined;
if (canvas) {
canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? (mixBlendMode as string) : '';
}
}, [mixBlendMode]);
useEffect(() => {
const program = programRef.current;
const renderer = rendererRef.current;
const gradTex = gradTexRef.current;
if (!program || !renderer || !gradTex) return;
program.uniforms.uIntensity.value = intensity ?? 1;
program.uniforms.uSpeed.value = speed ?? 1;
const animTypeMap: Record<AnimationType, number> = {
rotate: 0,
rotate3d: 1,
hover: 2
};
program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate3d'];
program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;
const ox = toPx(offset?.x);
const oy = toPx(offset?.y);
program.uniforms.uOffset.value = [ox, oy];
program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));
let count = 0;
if (Array.isArray(colors) && colors.length > 0) {
const gl = renderer.gl;
const capped = colors.slice(0, 64);
count = capped.length;
const data = new Uint8Array(count * 4);
for (let i = 0; i < count; i++) {
const [r, g, b] = hexToRgb01(capped[i]);
data[i * 4 + 0] = Math.round(r * 255);
data[i * 4 + 1] = Math.round(g * 255);
data[i * 4 + 2] = Math.round(b * 255);
data[i * 4 + 3] = 255;
}
gradTex.image = data;
gradTex.width = count;
gradTex.height = 1;
gradTex.needsUpdate = true;
}
program.uniforms.uColorCount.value = count;
}, [intensity, speed, animationType, colors, distort, offset, rayCount]);
return (
<div className={`w-full h-full relative overflow-hidden ${className}`} ref={containerRef} />
);
}
export default PrismaticBurst;
~~~
Implementation Guidelines
1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system
Help me integrate this into my design