Загрузка...

Анимированный фон WebGL с лучами света. Настраиваемый, интерактивный, идеален для динамических заголовков и секций.
<div class="light-rays-container" id="lightRaysWrap"></div>
<style>
.light-rays-container {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none; /* så att den inte stör klick */
z-index: -1; /* bakgrund */
}
canvas#light-rays-canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
<script>
(function(){
const container = document.getElementById('lightRaysWrap');
const canvas = document.createElement('canvas');
canvas.id = 'light-rays-canvas';
container.appendChild(canvas);
const gl = canvas.getContext('webgl', { alpha: true, antialias: true });
if (!gl) { console.error('WebGL not supported'); return; }
// --- Shader helpers ---
function compileShader(gl,type,src){const s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){console.error(gl.getShaderInfoLog(s));gl.deleteShader(s);return null;}return s;}
function createProgram(gl,vs,fs){const v=compileShader(gl,gl.VERTEX_SHADER,vs),f=compileShader(gl,gl.FRAGMENT_SHADER,fs);if(!v||!f)return null;const p=gl.createProgram();gl.attachShader(p,v);gl.attachShader(p,f);gl.linkProgram(p);if(!gl.getProgramParameter(p,gl.LINK_STATUS)){console.error(gl.getProgramInfoLog(p));gl.deleteProgram(p);return null;}return p;}
const vertexSrc = `attribute vec2 a_position; varying vec2 vUv; void main(){vUv=a_position*0.5+0.5; gl_Position=vec4(a_position,0.0,1.0);}`;
const fragmentSrc = `precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 rayPos;
uniform vec2 rayDir;
uniform vec3 raysColor;
uniform float raysSpeed;
uniform float lightSpread;
uniform float rayLength;
uniform float pulsating;
uniform float fadeDistance;
uniform float saturation;
uniform vec2 mousePos;
uniform float mouseInfluence;
uniform float noiseAmount;
uniform float distortion;
varying vec2 vUv;
float noise(vec2 st){return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);}
float rayStrength(vec2 raySource,vec2 rayRefDirection,vec2 coord,float seedA,float seedB,float speed){
vec2 sourceToCoord=coord-raySource;
vec2 dirNorm=normalize(sourceToCoord);
float cosAngle=dot(dirNorm,rayRefDirection);
float distortedAngle=cosAngle+distortion*sin(iTime*2.0+length(sourceToCoord)*0.01)*0.2;
float spreadFactor=pow(max(distortedAngle,0.0),1.0/max(lightSpread,0.001));
float distance=length(sourceToCoord);
float maxDistance=iResolution.x*rayLength;
float lengthFalloff=clamp((maxDistance-distance)/maxDistance,0.0,1.0);
float fadeFalloff=clamp((iResolution.x*fadeDistance-distance)/(iResolution.x*fadeDistance),0.5,1.0);
float pulse=pulsating>0.5?(0.8+0.2*sin(iTime*speed*3.0)):1.0;
float baseStrength=clamp((0.45+0.15*sin(distortedAngle*seedA+iTime*speed))+(0.3+0.2*cos(-distortedAngle*seedB+iTime*speed)),0.0,1.0);
return baseStrength*lengthFalloff*fadeFalloff*spreadFactor*pulse;
}
void mainImage(out vec4 fragColor,in vec2 fragCoord){
vec2 coord=vec2(fragCoord.x,iResolution.y-fragCoord.y);
vec2 finalRayDir=rayDir;
if(mouseInfluence>0.0){vec2 mouseScreenPos=mousePos*iResolution.xy;vec2 mouseDirection=normalize(mouseScreenPos-rayPos);finalRayDir=normalize(mix(rayDir,mouseDirection,mouseInfluence));}
vec4 rays1=vec4(1.0)*rayStrength(rayPos,finalRayDir,coord,36.2214,21.11349,1.5*raysSpeed);
vec4 rays2=vec4(1.0)*rayStrength(rayPos,finalRayDir,coord,22.3991,18.0234,1.1*raysSpeed);
fragColor=rays1*0.5+rays2*0.4;
if(noiseAmount>0.0){float n=noise(coord*0.01+iTime*0.1);fragColor.rgb*=(1.0-noiseAmount+noiseAmount*n);}
float brightness=1.0-(coord.y/iResolution.y);
fragColor.x*=(0.1+brightness*0.8);
fragColor.y*=(0.3+brightness*0.6);
fragColor.z*=(0.5+brightness*0.5);
if(saturation!=1.0){float gray=dot(fragColor.rgb,vec3(0.299,0.587,0.114));fragColor.rgb=mix(vec3(gray),fragColor.rgb,saturation);}
fragColor.rgb*=raysColor;
}
void main(){vec4 color; mainImage(color,gl_FragCoord.xy); gl_FragColor=color;}`;
const program = createProgram(gl, vertexSrc, fragmentSrc);
gl.useProgram(program);
const posLoc = gl.getAttribLocation(program,'a_position');
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,posBuf);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc,2,gl.FLOAT,false,0,0);
const uni = {};
['iTime','iResolution','rayPos','rayDir','raysColor','raysSpeed','lightSpread','rayLength','pulsating','fadeDistance','saturation','mousePos','mouseInfluence','noiseAmount','distortion'].forEach(n=>uni[n]=gl.getUniformLocation(program,n));
// --- Component state (uppdaterade värden) ---
const state = {
raysColor:'#dc143c',
raysOrigin:'top',
raysSpeed:2.2,
lightSpread:3.0,
rayLength:4.9,
pulsating:false,
fadeDistance:2.0,
saturation:0.6,
mouseInfluence:0.12,
noiseAmount:0,
distortion:0
};
function hexToRgb(hex){const m=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);return m?[parseInt(m[1],16)/255,parseInt(m[2],16)/255,parseInt(m[3],16)/255]:[1,1,1];}
function getAnchorAndDir(origin,w,h){const o=0.2;switch(origin){case'top-left':return{anchor:[0,-o*h],dir:[0,1]};case'top-right':return{anchor:[w,-o*h],dir:[0,1]};case'left':return{anchor:[-o*w,0.5*h],dir:[1,0]};case'right':return{anchor:[(1+o)*w,0.5*h],dir:[-1,0]};case'bottom-left':return{anchor:[0,(1+o)*h],dir:[0,-1]};case'bottom-center':return{anchor:[0.5*w,(1+o)*h],dir:[0,-1]};case'bottom-right':return{anchor:[w,(1+o)*h],dir:[0,-1]};default:return{anchor:[0.5*w,-o*h],dir:[0,1]};}}
const mouse={x:0.5,y:0.5},smoothMouse={x:0.5,y:0.5};
window.addEventListener('mousemove',e=>{const r=canvas.getBoundingClientRect();mouse.x=(e.clientX-r.left)/r.width;mouse.y=(e.clientY-r.top)/r.height;});
function resize(){const dpr=Math.min(window.devicePixelRatio||1,2);const w=Math.floor(container.clientWidth*dpr);const h=Math.floor(container.clientHeight*dpr);canvas.width=w;canvas.height=h;gl.viewport(0,0,w,h);}
window.addEventListener('resize',resize); resize();
function setUniforms(t){
gl.uniform1f(uni.iTime,t*0.001);
gl.uniform2f(uni.iResolution,canvas.width,canvas.height);
const {anchor,dir} = getAnchorAndDir(state.raysOrigin,canvas.width,canvas.height);
gl.uniform2f(uni.rayPos,anchor[0],anchor[1]);
gl.uniform2f(uni.rayDir,dir[0],dir[1]);
const c = hexToRgb(state.raysColor);
gl.uniform3f(uni.raysColor,c[0],c[1],c[2]);
gl.uniform1f(uni.raysSpeed,state.raysSpeed);
gl.uniform1f(uni.lightSpread,state.lightSpread);
gl.uniform1f(uni.rayLength,state.rayLength);
gl.uniform1f(uni.pulsating,state.pulsating?1:0);
gl.uniform1f(uni.fadeDistance,state.fadeDistance);
gl.uniform1f(uni.saturation,state.saturation);
gl.uniform2f(uni.mousePos,smoothMouse.x,smoothMouse.y);
gl.uniform1f(uni.mouseInfluence,state.mouseInfluence);
gl.uniform1f(uni.noiseAmount,state.noiseAmount);
gl.uniform1f(uni.distortion,state.distortion);
}
function frame(t){
const s=0.92;
smoothMouse.x=smoothMouse.x*s+(1-s)*mouse.x;
smoothMouse.y=smoothMouse.y*s+(1-s)*mouse.y;
setUniforms(t);
gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,6);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>