luminomorphism
Version:
A UI design system built around light, blur, ambient motion and perceptual feedback.
8 lines (7 loc) • 3.52 kB
JavaScript
class LGenerativeBg extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.particles=[],this.width=0,this.height=0,this.palette=["#00ffff","#ff00ff","#ffff00"],this.particleCount=20,this.speed=.5,this.cursor={x:null,y:null},this.resize=this.resize.bind(this),this.animate=this.animate.bind(this),this.onMouseMove=this.onMouseMove.bind(this)}static get observedAttributes(){return["palette","particlecount","speed"]}attributeChangedCallback(t,i,s){if(i!==s){if(t==="palette"&&(this.palette=s.split(",").map(e=>e.trim()).filter(Boolean)),t==="particlecount"){const e=parseInt(s);!isNaN(e)&&e>0&&(this.particleCount=e)}if(t==="speed"){const e=parseFloat(s);!isNaN(e)&&e>0&&(this.speed=e)}}}connectedCallback(){if(this.hasAttribute("palette")&&(this.palette=this.getAttribute("palette").split(",").map(i=>i.trim()).filter(Boolean)),this.hasAttribute("particlecount")){const i=parseInt(this.getAttribute("particlecount"));!isNaN(i)&&i>0&&(this.particleCount=i)}if(this.hasAttribute("speed")){const i=parseFloat(this.getAttribute("speed"));!isNaN(i)&&i>0&&(this.speed=i)}Object.assign(this.canvas.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"0"});const t=document.createElement("style");t.textContent=`
:host {
display: block;
position: relative;
overflow: hidden;
}
`,this.shadow.appendChild(t),this.shadow.appendChild(this.canvas),this.initParticles(),window.addEventListener("resize",this.resize),window.addEventListener("mousemove",this.onMouseMove),this.resize(),this.animate()}disconnectedCallback(){window.removeEventListener("resize",this.resize),window.removeEventListener("mousemove",this.onMouseMove)}initParticles(){this.particles=[];for(let t=0;t<this.particleCount;t++)this.particles.push(this.createParticle())}createParticle(){const t=Math.random()*2*Math.PI,i=this.speed+Math.random()*this.speed;return{x:Math.random()*this.width,y:Math.random()*this.height,vx:Math.cos(t)*i,vy:Math.sin(t)*i,color:this.palette[Math.floor(Math.random()*this.palette.length)],size:80+Math.random()*80}}resize(){const t=this.getBoundingClientRect();this.width=t.width,this.height=t.height,this.canvas.width=this.width,this.canvas.height=this.height,this.width>0&&this.height>0&&this.initParticles()}onMouseMove(t){const i=this.canvas.getBoundingClientRect();this.cursor.x=t.clientX-i.left,this.cursor.y=t.clientY-i.top}updateParticles(){for(const t of this.particles){if(t.x+=t.vx,t.y+=t.vy,t.x<-t.size&&(t.x=this.width+t.size),t.x>this.width+t.size&&(t.x=-t.size),t.y<-t.size&&(t.y=this.height+t.size),t.y>this.height+t.size&&(t.y=-t.size),this.cursor.x!==null&&this.cursor.y!==null){const i=this.cursor.x-t.x,s=this.cursor.y-t.y,e=i*i+s*s,h=200;if(e<h*h){const a=(1-Math.sqrt(e)/h)*.5;t.vx+=i*a*5e-4,t.vy+=s*a*5e-4}}t.vx*=.99,t.vy*=.99}}drawParticles(){const t=this.ctx;t.clearRect(0,0,this.width,this.height);for(const i of this.particles){const s=t.createRadialGradient(i.x,i.y,0,i.x,i.y,i.size);s.addColorStop(0,this.hexToRGBA(i.color,.6)),s.addColorStop(1,this.hexToRGBA(i.color,0)),t.beginPath(),t.fillStyle=s,t.arc(i.x,i.y,i.size,0,Math.PI*2),t.fill()}}hexToRGBA(t,i=1){const s=t.replace("#",""),e=parseInt(s,16),h=e>>16&255,a=e>>8&255,o=e&255;return`rgba(${h},${a},${o},${i})`}animate(){this.updateParticles(),this.drawParticles(),requestAnimationFrame(this.animate)}}customElements.define("l-generative-bg",LGenerativeBg);