Files
vue-bits/public/r/Orb.json
David Haz e621971723 jsrepo v3
2025-12-15 23:50:24 +02:00

1 line
9.4 KiB
JSON

{"name":"Orb","title":"Orb","description":"Floating energy orb with customizable hover effect.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { Renderer, Program, Mesh, Triangle, Vec3 } from 'ogl';\n\ninterface OrbProps {\n hue?: number;\n hoverIntensity?: number;\n rotateOnHover?: boolean;\n forceHoverState?: boolean;\n}\n\nconst props = withDefaults(defineProps<OrbProps>(), {\n hue: 0,\n hoverIntensity: 0.2,\n rotateOnHover: true,\n forceHoverState: false\n});\n\nconst ctnDom = useTemplateRef<HTMLDivElement>('ctnDom');\n\nconst vert = /* glsl */ `\n precision highp float;\n attribute vec2 position;\n attribute vec2 uv;\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\nconst frag = /* glsl */ `\n precision highp float;\n\n uniform float iTime;\n uniform vec3 iResolution;\n uniform float hue;\n uniform float hover;\n uniform float rot;\n uniform float hoverIntensity;\n varying vec2 vUv;\n\n vec3 rgb2yiq(vec3 c) {\n float y = dot(c, vec3(0.299, 0.587, 0.114));\n float i = dot(c, vec3(0.596, -0.274, -0.322));\n float q = dot(c, vec3(0.211, -0.523, 0.312));\n return vec3(y, i, q);\n }\n \n vec3 yiq2rgb(vec3 c) {\n float r = c.x + 0.956 * c.y + 0.621 * c.z;\n float g = c.x - 0.272 * c.y - 0.647 * c.z;\n float b = c.x - 1.106 * c.y + 1.703 * c.z;\n return vec3(r, g, b);\n }\n \n vec3 adjustHue(vec3 color, float hueDeg) {\n float hueRad = hueDeg * 3.14159265 / 180.0;\n vec3 yiq = rgb2yiq(color);\n float cosA = cos(hueRad);\n float sinA = sin(hueRad);\n float i = yiq.y * cosA - yiq.z * sinA;\n float q = yiq.y * sinA + yiq.z * cosA;\n yiq.y = i;\n yiq.z = q;\n return yiq2rgb(yiq);\n }\n \n vec3 hash33(vec3 p3) {\n p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787));\n p3 += dot(p3, p3.yxz + 19.19);\n return -1.0 + 2.0 * fract(vec3(\n p3.x + p3.y,\n p3.x + p3.z,\n p3.y + p3.z\n ) * p3.zyx);\n }\n \n float snoise3(vec3 p) {\n const float K1 = 0.333333333;\n const float K2 = 0.166666667;\n vec3 i = floor(p + (p.x + p.y + p.z) * K1);\n vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);\n vec3 e = step(vec3(0.0), d0 - d0.yzx);\n vec3 i1 = e * (1.0 - e.zxy);\n vec3 i2 = 1.0 - e.zxy * (1.0 - e);\n vec3 d1 = d0 - (i1 - K2);\n vec3 d2 = d0 - (i2 - K1);\n vec3 d3 = d0 - 0.5;\n vec4 h = max(0.6 - vec4(\n dot(d0, d0),\n dot(d1, d1),\n dot(d2, d2),\n dot(d3, d3)\n ), 0.0);\n vec4 n = h * h * h * h * vec4(\n dot(d0, hash33(i)),\n dot(d1, hash33(i + i1)),\n dot(d2, hash33(i + i2)),\n dot(d3, hash33(i + 1.0))\n );\n return dot(vec4(31.316), n);\n }\n \n vec4 extractAlpha(vec3 colorIn) {\n float a = max(max(colorIn.r, colorIn.g), colorIn.b);\n return vec4(colorIn.rgb / (a + 1e-5), a);\n }\n \n const vec3 baseColor1 = vec3(0.611765, 0.262745, 0.996078);\n const vec3 baseColor2 = vec3(0.298039, 0.760784, 0.913725);\n const vec3 baseColor3 = vec3(0.062745, 0.078431, 0.600000);\n const float innerRadius = 0.6;\n const float noiseScale = 0.65;\n \n float light1(float intensity, float attenuation, float dist) {\n return intensity / (1.0 + dist * attenuation);\n }\n \n float light2(float intensity, float attenuation, float dist) {\n return intensity / (1.0 + dist * dist * attenuation);\n }\n \n vec4 draw(vec2 uv) {\n vec3 color1 = adjustHue(baseColor1, hue);\n vec3 color2 = adjustHue(baseColor2, hue);\n vec3 color3 = adjustHue(baseColor3, hue);\n \n float ang = atan(uv.y, uv.x);\n float len = length(uv);\n float invLen = len > 0.0 ? 1.0 / len : 0.0;\n \n float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5;\n float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0);\n float d0 = distance(uv, (r0 * invLen) * uv);\n float v0 = light1(1.0, 10.0, d0);\n v0 *= smoothstep(r0 * 1.05, r0, len);\n float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5;\n \n float a = iTime * -1.0;\n vec2 pos = vec2(cos(a), sin(a)) * r0;\n float d = distance(uv, pos);\n float v1 = light2(1.5, 5.0, d);\n v1 *= light1(1.0, 50.0, d0);\n \n float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len);\n float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len);\n \n vec3 col = mix(color1, color2, cl);\n col = mix(color3, col, v0);\n col = (col + v1) * v2 * v3;\n col = clamp(col, 0.0, 1.0);\n \n return extractAlpha(col);\n }\n \n vec4 mainImage(vec2 fragCoord) {\n vec2 center = iResolution.xy * 0.5;\n float size = min(iResolution.x, iResolution.y);\n vec2 uv = (fragCoord - center) / size * 2.0;\n \n float angle = rot;\n float s = sin(angle);\n float c = cos(angle);\n uv = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y);\n \n uv.x += hover * hoverIntensity * 0.1 * sin(uv.y * 10.0 + iTime);\n uv.y += hover * hoverIntensity * 0.1 * sin(uv.x * 10.0 + iTime);\n \n return draw(uv);\n }\n \n void main() {\n vec2 fragCoord = vUv * iResolution.xy;\n vec4 col = mainImage(fragCoord);\n gl_FragColor = vec4(col.rgb * col.a, col.a);\n }\n `;\n\nlet cleanupAnimation: (() => void) | null = null;\n\nconst setupAnimation = () => {\n const container = ctnDom.value;\n if (!container) return;\n\n const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n container.appendChild(gl.canvas);\n\n const geometry = new Triangle(gl);\n const program = new Program(gl, {\n vertex: vert,\n fragment: frag,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Vec3(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n hue: { value: props.hue },\n hover: { value: 0 },\n rot: { value: 0 },\n hoverIntensity: { value: props.hoverIntensity }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!container) return;\n const dpr = window.devicePixelRatio || 1;\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width * dpr, height * dpr);\n gl.canvas.style.width = width + 'px';\n gl.canvas.style.height = height + 'px';\n program.uniforms.iResolution.value.set(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height);\n }\n window.addEventListener('resize', resize);\n resize();\n\n let targetHover = 0;\n let lastTime = 0;\n let currentRot = 0;\n const rotationSpeed = 0.3;\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n const width = rect.width;\n const height = rect.height;\n const size = Math.min(width, height);\n const centerX = width / 2;\n const centerY = height / 2;\n const uvX = ((x - centerX) / size) * 2.0;\n const uvY = ((y - centerY) / size) * 2.0;\n\n if (Math.sqrt(uvX * uvX + uvY * uvY) < 0.8) {\n targetHover = 1;\n } else {\n targetHover = 0;\n }\n };\n\n const handleMouseLeave = () => {\n targetHover = 0;\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n let rafId: number;\n const update = (t: number) => {\n rafId = requestAnimationFrame(update);\n const dt = (t - lastTime) * 0.001;\n lastTime = t;\n program.uniforms.iTime.value = t * 0.001;\n program.uniforms.hue.value = props.hue;\n program.uniforms.hoverIntensity.value = props.hoverIntensity;\n\n const effectiveHover = props.forceHoverState ? 1 : targetHover;\n program.uniforms.hover.value += (effectiveHover - program.uniforms.hover.value) * 0.1;\n\n if (props.rotateOnHover && effectiveHover > 0.5) {\n currentRot += dt * rotationSpeed;\n }\n program.uniforms.rot.value = currentRot;\n\n renderer.render({ scene: mesh });\n };\n rafId = requestAnimationFrame(update);\n\n cleanupAnimation = () => {\n cancelAnimationFrame(rafId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n};\n\nonMounted(() => {\n setupAnimation();\n});\n\nonUnmounted(() => {\n if (cleanupAnimation) {\n cleanupAnimation();\n cleanupAnimation = null;\n }\n});\n\nwatch(\n () => props,\n () => {\n if (cleanupAnimation) {\n cleanupAnimation();\n cleanupAnimation = null;\n }\n setupAnimation();\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div ref=\"ctnDom\" class=\"w-full h-full\" />\n</template>\n","path":"Orb/Orb.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}],"devDependencies":[],"categories":["Backgrounds"]}