aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
399 lines (349 loc) • 12.1 kB
JavaScript
'use client';
import { jsx } from 'react/jsx-runtime';
import { useRef, useState, useEffect } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
// Vertex shader (common for all variants)
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
// Fragment shaders for different glass effects
const fragmentShaders = {
refraction: `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform float u_intensity;
varying vec2 v_texCoord;
void main() {
vec2 uv = v_texCoord;
vec2 center = vec2(0.5, 0.5);
vec2 mouseNorm = u_mouse / u_resolution;
// Calculate refraction based on distance from mouse
float dist = distance(uv, mouseNorm);
float refraction = sin(dist * 10.0 - u_time * 2.0) * 0.02 * u_intensity;
// Apply chromatic aberration
vec2 rOffset = uv + vec2(refraction, 0.0);
vec2 gOffset = uv;
vec2 bOffset = uv - vec2(refraction, 0.0);
float r = texture2D(u_image, rOffset).r;
float g = texture2D(u_image, gOffset).g;
float b = texture2D(u_image, bOffset).b;
gl_FragColor = vec4(r, g, b, 1.0);
}
`,
dispersion: `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_intensity;
varying vec2 v_texCoord;
void main() {
vec2 uv = v_texCoord;
// Rainbow dispersion effect
float wave = sin(uv.y * 10.0 + u_time) * 0.01 * u_intensity;
vec2 rUV = uv + vec2(wave * 2.0, 0.0);
vec2 gUV = uv + vec2(wave, 0.0);
vec2 bUV = uv;
vec3 color;
color.r = texture2D(u_image, rUV).r;
color.g = texture2D(u_image, gUV).g;
color.b = texture2D(u_image, bUV).b;
// Add prismatic highlights
float highlight = sin(uv.x * 20.0 + u_time * 2.0) * 0.1;
color += vec3(highlight, highlight * 0.5, highlight * 0.3);
gl_FragColor = vec4(color, 1.0);
}
`,
frosted: `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_intensity;
varying vec2 v_texCoord;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
void main() {
vec2 uv = v_texCoord;
vec3 color = vec3(0.0);
// Frosted glass blur
float blurSize = 0.01 * u_intensity;
int samples = 9;
for(int i = 0; i < samples; i++) {
float angle = float(i) / float(samples) * 6.28318;
vec2 offset = vec2(cos(angle), sin(angle)) * blurSize;
offset *= random(uv + float(i));
color += texture2D(u_image, uv + offset).rgb;
}
color /= float(samples);
// Add frost texture
float frost = random(uv * 100.0 + u_time * 0.1) * 0.1;
color += vec3(frost);
gl_FragColor = vec4(color, 1.0);
}
`,
crystal: `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform float u_intensity;
varying vec2 v_texCoord;
void main() {
vec2 uv = v_texCoord;
vec2 center = vec2(0.5, 0.5);
// Crystal facets
float angle = atan(uv.y - center.y, uv.x - center.x);
float facets = 8.0;
angle = floor(angle * facets) / facets;
float dist = distance(uv, center);
vec2 facetUV = center + vec2(cos(angle), sin(angle)) * dist;
// Refraction through crystal
vec2 refractUV = mix(uv, facetUV, u_intensity * 0.5);
vec3 color = texture2D(u_image, refractUV).rgb;
// Add sparkle
float sparkle = sin(angle * 20.0 + u_time * 3.0) * 0.2;
color += vec3(sparkle);
// Inner reflections
float reflection = sin(dist * 30.0 - u_time * 2.0) * 0.1;
color += vec3(reflection, reflection * 0.8, reflection * 1.2);
gl_FragColor = vec4(color, 1.0);
}
`,
prism: `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_intensity;
varying vec2 v_texCoord;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec2 uv = v_texCoord;
// Prism light splitting
float prismAngle = uv.x + sin(uv.y * 10.0 + u_time) * 0.1;
// Sample at different wavelengths
vec3 color = vec3(0.0);
int samples = 7;
for(int i = 0; i < samples; i++) {
float wavelength = float(i) / float(samples);
vec2 offset = vec2(wavelength * 0.02 * u_intensity, 0.0);
vec3 sample = texture2D(u_image, uv + offset).rgb;
// Apply wavelength-based tinting
vec3 tint = hsv2rgb(vec3(wavelength, 0.8, 1.0));
color += sample * tint;
}
color /= float(samples);
// Add rainbow spectrum overlay
float spectrum = sin(prismAngle * 20.0) * 0.3;
vec3 rainbow = hsv2rgb(vec3(prismAngle, 1.0, spectrum));
color = mix(color, rainbow, 0.2 * u_intensity);
gl_FragColor = vec4(color, 1.0);
}
`
};
function GlassWebGLShader({
className,
variant = "refraction",
intensity = 1,
animated = true,
interactive = true,
backgroundColor = "transparent"
}) {
const canvasRef = useRef(null);
const glRef = useRef(null);
const programRef = useRef(null);
const animationRef = useRef(0);
const mouseRef = useRef({
x: 0,
y: 0
});
const [isSupported, setIsSupported] = useState(true);
// Initialize WebGL
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
setIsSupported(false);
console.warn("WebGL not supported");
return;
}
glRef.current = gl;
// Create shaders
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaders[variant]);
if (!vertexShader || !fragmentShader) {
setIsSupported(false);
return;
}
// Create program
const program = createProgram(gl, vertexShader, fragmentShader);
if (!program) {
setIsSupported(false);
return;
}
programRef.current = program;
// Set up geometry
setupGeometry(gl, program);
// Set up texture
setupTexture(gl);
// Start render loop
if (animated) {
render();
}
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [variant, animated]);
// Handle mouse interaction
useEffect(() => {
if (!interactive) return;
const handleMouseMove = e => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
mouseRef.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, [interactive]);
// Create shader
function createShader(gl, type, source) {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("Shader compilation error:", gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// Create program
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Program linking error:", gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
// Set up geometry
function setupGeometry(gl, program) {
// Positions
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// Texture coordinates
const texCoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
}
// Set up texture
function setupTexture(gl) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Create gradient texture
const width = 256;
const height = 256;
const pixels = new Uint8Array(width * height * 4);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
pixels[i] = x / width * 255; // R
pixels[i + 1] = y / height * 255; // G
pixels[i + 2] = 128; // B
pixels[i + 3] = 255; // A
}
}
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
}
// Render loop
function render() {
const gl = glRef.current;
const program = programRef.current;
const canvas = canvasRef.current;
if (!gl || !program || !canvas) return;
// Clear canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Use program
gl.useProgram(program);
// Set uniforms
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
const mouseLocation = gl.getUniformLocation(program, "u_mouse");
gl.uniform2f(mouseLocation, mouseRef.current.x, mouseRef.current.y);
const timeLocation = gl.getUniformLocation(program, "u_time");
gl.uniform1f(timeLocation, performance.now() / 1000);
const intensityLocation = gl.getUniformLocation(program, "u_intensity");
gl.uniform1f(intensityLocation, intensity);
// Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// Continue animation
if (animated) {
animationRef.current = requestAnimationFrame(render);
}
}
if (!isSupported) {
return jsx("div", {
"data-glass-component": true,
className: cn("OptimizedGlass intensity={0.2} glassBlur={6} glass-p-4", className),
children: jsx("p", {
className: "glass-text-secondary",
children: "WebGL not supported"
})
});
}
return jsx("div", {
className: cn("relative overflow-hidden", className),
style: {
backgroundColor
},
children: jsx("canvas", {
ref: canvasRef,
className: 'absolute inset-0 glass-w-full glass-h-full',
width: 800,
height: 600
})
});
}
export { GlassWebGLShader };
//# sourceMappingURL=GlassWebGLShader.js.map