FEAT: 🎉 Add New <Aurora /> background!

This commit is contained in:
David Haz
2025-02-05 13:23:12 +02:00
parent bbcac1c856
commit e74389487d
13 changed files with 993 additions and 2 deletions
+5
View File
@@ -19,6 +19,8 @@ const componentNameLower = componentName.charAt(0).toLowerCase() + componentName
const paths = {
content: path.join(__dirname, "../src/content", componentType, componentName),
tailwind: path.join(__dirname, "../src/tailwind", componentType, componentName),
ts: path.join(__dirname, "../src/ts-default", componentType, componentName),
tsTailwind: path.join(__dirname, "../src/ts-tailwind", componentType, componentName),
demo: path.join(__dirname, "../src/demo", componentType),
constants: path.join(__dirname, "../src/constants/code", componentType),
};
@@ -33,6 +35,9 @@ const files = [
path.join(paths.content, `${componentName}.jsx`),
path.join(paths.content, `${componentName}.css`),
path.join(paths.tailwind, `${componentName}.jsx`),
path.join(paths.ts, `${componentName}.tsx`),
path.join(paths.ts, `${componentName}.css`),
path.join(paths.tsTailwind, `${componentName}.tsx`),
path.join(paths.demo, `${componentName}Demo.jsx`),
path.join(paths.constants, `${componentNameLower}Code.js`),
];
+3 -2
View File
@@ -1,6 +1,6 @@
// Highlighted sidebar items
export const NEW = ['Stepper', 'Infinite Menu', 'Flying Posters', 'Image Trail', 'Flowing Menu', 'Pixel Trail', 'Rotating Text', 'Circular Gallery'];
export const UPDATED = ['Follow Cursor'];
export const NEW = ['Infinite Menu', 'Flying Posters', 'Pixel Trail', 'Circular Gallery', 'Aurora'];
export const UPDATED = [''];
// Used for main sidebar navigation
export const CATEGORIES = [
@@ -97,6 +97,7 @@ export const CATEGORIES = [
{
name: 'Backgrounds',
subcategories: [
'Aurora',
'Shape Blur',
'Hyperspeed',
'Grid Distortion',
+1
View File
@@ -51,6 +51,7 @@ const components = {
};
const backgrounds = {
'aurora': () => import("../demo/Backgrounds/AuroraDemo"),
'squares': () => import("../demo/Backgrounds/SquaresDemo"),
'hyperspeed': () => import("../demo/Backgrounds/HyperspeedDemo"),
'grid-motion': () => import("../demo/Backgrounds/GridMotionDemo"),
@@ -0,0 +1,23 @@
import { generateCliCommands } from '@/utils/utils';
import code from '@content/Backgrounds/Aurora/Aurora.jsx?raw';
import css from '@content/Backgrounds/Aurora/Aurora.css?raw';
import tailwind from '@tailwind/Backgrounds/Aurora/Aurora.jsx?raw';
import tsCode from '@ts-default/Backgrounds/Aurora/Aurora.tsx?raw';
import tsTailwind from '@ts-tailwind/Backgrounds/Aurora/Aurora.tsx?raw';
export const aurora = {
...(generateCliCommands('Backgrounds/Aurora')),
installation: `npm i ogl`,
usage: `import Aurora from './Aurora';
<Aurora
colors={["#3A29FF", "#FF94B4", "#FF3232"]}
speed={0.5}
/>`,
code,
css,
tailwind,
tsCode,
tsTailwind
}
@@ -8,6 +8,7 @@ import tsTailwind from '@ts-tailwind/Components/Stepper/Stepper.tsx?raw';
export const stepper = {
...(generateCliCommands('Components/Stepper')),
installation: `npm i framer-motion`,
usage: `import Stepper, { Step } from './Stepper';
<Stepper
@@ -9,6 +9,7 @@ import tsTailwind from '@ts-tailwind/TextAnimations/RotatingText/RotatingText.ts
export const rotatingText = {
...(generateCliCommands('TextAnimations/RotatingText')),
installation: `npm i framer-motion`,
usage: `import RotatingText from './RotatingText'
<RotatingText
@@ -0,0 +1,4 @@
.aurora-container {
width: 100%;
height: 100%;
}
+197
View File
@@ -0,0 +1,197 @@
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import { useEffect, useRef } from "react";
import './Aurora.css';
const VERT = `#version 300 es
in vec2 uv;
in vec2 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform float uAmplitude;
uniform vec3 uColorStops[3];
in vec2 vUv;
out vec4 fragColor;
vec3 permute(vec3 x) {
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(
0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(
permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0)
);
vec3 m = max(
0.5 - vec3(
dot(x0, x0),
dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)
),
0.0
);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
struct ColorStop {
vec3 color;
float position;
};
/**
* Helper macro to blend between consecutive ColorStops
* based on vUv.x
*/
#define COLOR_RAMP(colors, factor, finalColor) { \
int index = 0; \
for (int i = 0; i < colors.length() - 1; i++) { \
ColorStop currentColor = colors[i]; \
bool isInBetween = currentColor.position <= factor; \
index = int(mix(float(index), float(i), float(isInBetween))); \
} \
ColorStop currentColor = colors[index]; \
ColorStop nextColor = colors[index + 1]; \
float range = nextColor.position - currentColor.position; \
float lerpFactor = (factor - currentColor.position) / range; \
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
}
void main() {
// Build our three color stops from uniform array uColorStops
ColorStop colors[3];
colors[0] = ColorStop(uColorStops[0], 0.0);
colors[1] = ColorStop(uColorStops[1], 0.5);
colors[2] = ColorStop(uColorStops[2], 1.0);
// Interpolate color along vUv.x
vec3 rampColor;
COLOR_RAMP(colors, vUv.x, rampColor);
// Noise-based "height," scaled by amplitude
float height = snoise(vec2(vUv.x * 2.0 + uTime * 0.1, uTime * 0.25))
* 0.5
* uAmplitude;
height = exp(height);
height = (vUv.y * 2.0 - height + 0.2);
fragColor.rgb = 0.6 * height * rampColor;
fragColor.a = 1.0;
}
`;
export default function Aurora(props) {
const { colorStops = ["#00d8ff", "#7cff67", "#00d8ff"], amplitude = 1.0 } =
props;
const propsRef = useRef(props);
propsRef.current = props;
const ctnDom = useRef(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
function resize() {
if (!ctn) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
}
window.addEventListener("resize", resize);
resize();
const geometry = new Triangle(gl);
geometry.addAttribute("uv", {
size: 2,
data: new Float32Array([0, 0, 2, 0, 0, 2]),
});
const colorStopsArray = colorStops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uAmplitude: { value: amplitude },
uColorStops: { value: colorStopsArray },
},
});
const mesh = new Mesh(gl, { geometry, program });
ctn.appendChild(gl.canvas);
let animateId = 0;
const update = (t) => {
animateId = requestAnimationFrame(update);
const { time = t * 0.01, speed = 1.0 } = propsRef.current;
program.uniforms.uTime.value = time * speed * 0.1;
program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;
const stops = propsRef.current.colorStops ?? colorStops;
program.uniforms.uColorStops.value = stops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
renderer.render({ scene: mesh });
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener("resize", resize);
ctn?.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [amplitude, colorStops]);
return <div ref={ctnDom} className="aurora-container" />;
}
+133
View File
@@ -0,0 +1,133 @@
import { CodeTab, PreviewTab, CliTab, TabbedLayout } from "../../components/common/TabbedLayout";
import { Box, Flex, Slider, SliderFilledTrack, SliderThumb, SliderTrack, Text } from "@chakra-ui/react";
import CodeExample from "../../components/code/CodeExample";
import CliInstallation from "../../components/code/CliInstallation";
import PropTable from "../../components/common/PropTable";
import Dependencies from '../../components/code/Dependencies';
import { useState } from "react";
import useForceRerender from "../../hooks/useForceRerender";
import Aurora from "../../content/Backgrounds/Aurora/Aurora";
import { aurora } from "../../constants/code/Backgrounds/auroraCode";
const AuroraDemo = () => {
const [color1, setColor1] = useState('#00d8ff');
const [color2, setColor2] = useState('#7cff67');
const [color3, setColor3] = useState('#00d8ff');
const [speed, setSpeed] = useState(1);
const [key, forceRerender] = useForceRerender();
const propData = [
{
name: "speed",
type: "number",
default: "1.0",
description: "Controls the animation speed. Higher values make the aurora move faster.",
},
{
name: "colorStops",
type: "[string, string, string]",
default: '["#3A29FF", "#FF94B4", "#FF3232"]',
description: "An array of three hex colors defining the aurora gradient.",
},
{
name: "amplitude",
type: "number",
default: "1.0",
description: "Controls the height intensity of the aurora effect.",
}
];
return (
<TabbedLayout>
<PreviewTab>
<Box position="relative" className="demo-container" h={500} p={0} overflow="hidden">
<Aurora key={key} speed={speed} colorStops={[color1, color2, color3]} />
</Box>
<div className="preview-options">
<h2 className="demo-title-extra">Options</h2>
<Flex gap={4} mb={2}>
<Flex alignItems="center">
<Text mr={2}>Color 1</Text>
<input
type="color"
value={color1}
style={{ height: '22px', outline: 'none', border: 'none' }}
onChange={(e) => {
setColor1(e.target.value);
forceRerender();
}}
/>
</Flex>
<Flex alignItems="center">
<Text mr={2}>Color 2</Text>
<input
type="color"
value={color2}
style={{ height: '22px', outline: 'none', border: 'none' }}
onChange={(e) => {
setColor2(e.target.value);
forceRerender();
}}
/>
</Flex>
<Flex alignItems="center">
<Text mr={2}>Color 3</Text>
<input
type="color"
value={color3}
style={{ height: '22px', outline: 'none', border: 'none' }}
onChange={(e) => {
setColor3(e.target.value);
forceRerender();
}}
/>
</Flex>
</Flex>
<Flex gap={4} align="center" mt={4}>
<Text fontSize="sm">Speed</Text>
<Slider
min={0}
max={2}
step={0.1}
value={speed}
onChange={(val) => {
setSpeed(val);
forceRerender();
}}
width="200px"
>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<Text fontSize="sm">{speed}</Text>
</Flex>
</div>
<PropTable data={propData} />
<Dependencies dependencyList={['ogl']} />
</PreviewTab>
<CodeTab>
<CodeExample codeObject={aurora} />
</CodeTab>
<CliTab>
<CliInstallation {...aurora} />
</CliTab>
</TabbedLayout>
);
};
export default AuroraDemo;
+195
View File
@@ -0,0 +1,195 @@
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import { useEffect, useRef } from "react";
const VERT = `#version 300 es
in vec2 uv;
in vec2 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform float uAmplitude;
uniform vec3 uColorStops[3];
in vec2 vUv;
out vec4 fragColor;
vec3 permute(vec3 x) {
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(
0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(
permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0)
);
vec3 m = max(
0.5 - vec3(
dot(x0, x0),
dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)
),
0.0
);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
struct ColorStop {
vec3 color;
float position;
};
/**
* Helper macro to blend between consecutive ColorStops
* based on vUv.x
*/
#define COLOR_RAMP(colors, factor, finalColor) { \
int index = 0; \
for (int i = 0; i < colors.length() - 1; i++) { \
ColorStop currentColor = colors[i]; \
bool isInBetween = currentColor.position <= factor; \
index = int(mix(float(index), float(i), float(isInBetween))); \
} \
ColorStop currentColor = colors[index]; \
ColorStop nextColor = colors[index + 1]; \
float range = nextColor.position - currentColor.position; \
float lerpFactor = (factor - currentColor.position) / range; \
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
}
void main() {
// Build our three color stops from uniform array uColorStops
ColorStop colors[3];
colors[0] = ColorStop(uColorStops[0], 0.0);
colors[1] = ColorStop(uColorStops[1], 0.5);
colors[2] = ColorStop(uColorStops[2], 1.0);
// Interpolate color along vUv.x
vec3 rampColor;
COLOR_RAMP(colors, vUv.x, rampColor);
// Noise-based "height," scaled by amplitude
float height = snoise(vec2(vUv.x * 2.0 + uTime * 0.1, uTime * 0.25))
* 0.5
* uAmplitude;
height = exp(height);
height = (vUv.y * 2.0 - height + 0.2);
fragColor.rgb = 0.6 * height * rampColor;
fragColor.a = 1.0;
}
`;
export default function Aurora(props) {
const { colorStops = ["#00d8ff", "#7cff67", "#00d8ff"], amplitude = 1.0 } =
props;
const propsRef = useRef(props);
propsRef.current = props;
const ctnDom = useRef(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
function resize() {
if (!ctn) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
}
window.addEventListener("resize", resize);
resize();
const geometry = new Triangle(gl);
geometry.addAttribute("uv", {
size: 2,
data: new Float32Array([0, 0, 2, 0, 0, 2]),
});
const colorStopsArray = colorStops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uAmplitude: { value: amplitude },
uColorStops: { value: colorStopsArray },
},
});
const mesh = new Mesh(gl, { geometry, program });
ctn.appendChild(gl.canvas);
let animateId = 0;
const update = (t) => {
animateId = requestAnimationFrame(update);
const { time = t * 0.01, speed = 1.0 } = propsRef.current;
program.uniforms.uTime.value = time * speed * 0.1;
program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;
const stops = propsRef.current.colorStops ?? colorStops;
program.uniforms.uColorStops.value = stops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
renderer.render({ scene: mesh });
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener("resize", resize);
ctn?.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [amplitude, colorStops]);
return <div ref={ctnDom} className="w-full h-full" />;
}
@@ -0,0 +1,4 @@
.aurora-container {
width: 100%;
height: 100%;
}
@@ -0,0 +1,214 @@
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import { useEffect, useRef } from "react";
import './Aurora.css';
export interface CommonProps {
onReady?: () => void;
}
export interface TimeProps {
time?: number;
speed?: number;
}
export interface ControlProps {
paused?: boolean;
}
export interface AuroraProps extends CommonProps, TimeProps, ControlProps {
colorStops?: [string, string, string];
amplitude?: number;
}
const VERT = `#version 300 es
in vec2 uv;
in vec2 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform float uAmplitude;
uniform vec3 uColorStops[3];
in vec2 vUv;
out vec4 fragColor;
vec3 permute(vec3 x) {
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(
0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(
permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0)
);
vec3 m = max(
0.5 - vec3(
dot(x0, x0),
dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)
),
0.0
);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
struct ColorStop {
vec3 color;
float position;
};
/**
* Helper macro to blend between consecutive ColorStops
* based on vUv.x
*/
#define COLOR_RAMP(colors, factor, finalColor) { \
int index = 0; \
for (int i = 0; i < colors.length() - 1; i++) { \
ColorStop currentColor = colors[i]; \
bool isInBetween = currentColor.position <= factor; \
index = int(mix(float(index), float(i), float(isInBetween))); \
} \
ColorStop currentColor = colors[index]; \
ColorStop nextColor = colors[index + 1]; \
float range = nextColor.position - currentColor.position; \
float lerpFactor = (factor - currentColor.position) / range; \
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
}
void main() {
// Build our three color stops from uniform array uColorStops
ColorStop colors[3];
colors[0] = ColorStop(uColorStops[0], 0.0);
colors[1] = ColorStop(uColorStops[1], 0.5);
colors[2] = ColorStop(uColorStops[2], 1.0);
// Interpolate color along vUv.x
vec3 rampColor;
COLOR_RAMP(colors, vUv.x, rampColor);
// Noise-based "height," scaled by amplitude
float height = snoise(vec2(vUv.x * 2.0 + uTime * 0.1, uTime * 0.25))
* 0.5
* uAmplitude;
height = exp(height);
height = (vUv.y * 2.0 - height + 0.2);
fragColor.rgb = 0.6 * height * rampColor;
fragColor.a = 1.0;
}
`;
export default function Aurora(props: AuroraProps) {
const { colorStops = ["#00d8ff", "#7cff67", "#00d8ff"], amplitude = 1.0 } =
props;
const propsRef = useRef(props);
propsRef.current = props;
const ctnDom = useRef<HTMLDivElement>(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
function resize() {
if (!ctn) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
}
window.addEventListener("resize", resize);
resize();
const geometry = new Triangle(gl);
geometry.addAttribute("uv", {
size: 2,
data: new Float32Array([0, 0, 2, 0, 0, 2]),
});
const colorStopsArray = colorStops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b] as [number, number, number];
});
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uAmplitude: { value: amplitude },
uColorStops: { value: colorStopsArray },
},
});
const mesh = new Mesh(gl, { geometry, program });
ctn.appendChild(gl.canvas);
let animateId = 0;
const update = (t: number) => {
animateId = requestAnimationFrame(update);
const { time = t * 0.01, speed = 1.0 } = propsRef.current;
program.uniforms.uTime.value = time * speed * 0.1;
program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;
const stops = propsRef.current.colorStops ?? colorStops;
program.uniforms.uColorStops.value = stops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b] as [number, number, number];
});
renderer.render({ scene: mesh });
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener("resize", resize);
ctn?.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
};
}, [amplitude, colorStops]);
return <div ref={ctnDom} className="aurora-container" />;
}
@@ -0,0 +1,212 @@
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import { useEffect, useRef } from "react";
export interface CommonProps {
onReady?: () => void;
}
export interface TimeProps {
time?: number;
speed?: number;
}
export interface ControlProps {
paused?: boolean;
}
export interface AuroraProps extends CommonProps, TimeProps, ControlProps {
colorStops?: [string, string, string];
amplitude?: number;
}
const VERT = `#version 300 es
in vec2 uv;
in vec2 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform float uAmplitude;
uniform vec3 uColorStops[3];
in vec2 vUv;
out vec4 fragColor;
vec3 permute(vec3 x) {
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(
0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(
permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0)
);
vec3 m = max(
0.5 - vec3(
dot(x0, x0),
dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)
),
0.0
);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
struct ColorStop {
vec3 color;
float position;
};
/**
* Helper macro to blend between consecutive ColorStops
* based on vUv.x
*/
#define COLOR_RAMP(colors, factor, finalColor) { \
int index = 0; \
for (int i = 0; i < colors.length() - 1; i++) { \
ColorStop currentColor = colors[i]; \
bool isInBetween = currentColor.position <= factor; \
index = int(mix(float(index), float(i), float(isInBetween))); \
} \
ColorStop currentColor = colors[index]; \
ColorStop nextColor = colors[index + 1]; \
float range = nextColor.position - currentColor.position; \
float lerpFactor = (factor - currentColor.position) / range; \
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
}
void main() {
// Build our three color stops from uniform array uColorStops
ColorStop colors[3];
colors[0] = ColorStop(uColorStops[0], 0.0);
colors[1] = ColorStop(uColorStops[1], 0.5);
colors[2] = ColorStop(uColorStops[2], 1.0);
// Interpolate color along vUv.x
vec3 rampColor;
COLOR_RAMP(colors, vUv.x, rampColor);
// Noise-based "height," scaled by amplitude
float height = snoise(vec2(vUv.x * 2.0 + uTime * 0.1, uTime * 0.25))
* 0.5
* uAmplitude;
height = exp(height);
height = (vUv.y * 2.0 - height + 0.2);
fragColor.rgb = 0.6 * height * rampColor;
fragColor.a = 1.0;
}
`;
export default function Aurora(props: AuroraProps) {
const { colorStops = ["#00d8ff", "#7cff67", "#00d8ff"], amplitude = 1.0 } =
props;
const propsRef = useRef(props);
propsRef.current = props;
const ctnDom = useRef<HTMLDivElement>(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
function resize() {
if (!ctn) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
}
window.addEventListener("resize", resize);
resize();
const geometry = new Triangle(gl);
geometry.addAttribute("uv", {
size: 2,
data: new Float32Array([0, 0, 2, 0, 0, 2]),
});
const colorStopsArray = colorStops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b] as [number, number, number];
});
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uAmplitude: { value: amplitude },
uColorStops: { value: colorStopsArray },
},
});
const mesh = new Mesh(gl, { geometry, program });
ctn.appendChild(gl.canvas);
let animateId = 0;
const update = (t: number) => {
animateId = requestAnimationFrame(update);
const { time = t * 0.01, speed = 1.0 } = propsRef.current;
program.uniforms.uTime.value = time * speed * 0.1;
program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;
const stops = propsRef.current.colorStops ?? colorStops;
program.uniforms.uColorStops.value = stops.map((hex) => {
const c = new Color(hex);
return [c.r, c.g, c.b] as [number, number, number];
});
renderer.render({ scene: mesh });
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener("resize", resize);
ctn?.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
};
}, [amplitude, colorStops]);
return <div ref={ctnDom} className="w-full h-full" />;
}