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

1 line
12 KiB
JSON

{"name":"GradientBlinds","title":"GradientBlinds","description":"Layered gradient blinds with spotlight and noise distortion.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch, type CSSProperties } from 'vue';\n\nexport interface GradientBlindsProps {\n className?: string;\n dpr?: number;\n paused?: boolean;\n gradientColors?: string[];\n angle?: number;\n noise?: number;\n blindCount?: number;\n blindMinWidth?: number;\n mouseDampening?: number;\n mirrorGradient?: boolean;\n spotlightRadius?: number;\n spotlightSoftness?: number;\n spotlightOpacity?: number;\n distortAmount?: number;\n shineDirection?: 'left' | 'right';\n mixBlendMode?: string;\n}\n\nconst MAX_COLORS = 8;\n\nconst hexToRGB = (hex: string): [number, number, number] => {\n const c = hex.replace('#', '').padEnd(6, '0');\n const r = parseInt(c.slice(0, 2), 16) / 255;\n const g = parseInt(c.slice(2, 4), 16) / 255;\n const b = parseInt(c.slice(4, 6), 16) / 255;\n return [r, g, b];\n};\n\nconst prepStops = (stops?: string[]) => {\n const base = (stops && stops.length ? stops : ['#FF9FFC', '#27FF64']).slice(0, MAX_COLORS);\n if (base.length === 1) base.push(base[0]);\n while (base.length < MAX_COLORS) base.push(base[base.length - 1]);\n const arr: [number, number, number][] = [];\n for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[i]));\n const count = Math.max(2, Math.min(MAX_COLORS, stops?.length ?? 2));\n return { arr, count };\n};\n\nconst props = withDefaults(defineProps<GradientBlindsProps>(), {\n paused: false,\n angle: 0,\n noise: 0.3,\n blindCount: 16,\n blindMinWidth: 60,\n mouseDampening: 0.15,\n mirrorGradient: false,\n spotlightRadius: 0.5,\n spotlightSoftness: 1,\n spotlightOpacity: 1,\n distortAmount: 0,\n shineDirection: 'left',\n mixBlendMode: 'lighten'\n});\n\nconst containerRef = useTemplateRef('containerRef');\nconst rafRef = ref<number>(0);\nconst programRef = ref<Program | null>(null);\nconst meshRef = ref<Mesh<Triangle> | null>(null);\nconst geometryRef = ref<Triangle | null>(null);\nconst rendererRef = ref<Renderer | null>(null);\nconst mouseTargetRef = ref<[number, number]>([0, 0]);\nconst lastTimeRef = ref<number>(0);\nconst firstResizeRef = ref<boolean>(true);\n\nlet cleanup: (() => void) | null = null;\n\nconst setup = () => {\n const container = containerRef.value;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: props.dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),\n alpha: true,\n antialias: true\n });\n rendererRef.value = renderer;\n const gl = renderer.gl;\n const canvas = gl.canvas as HTMLCanvasElement;\n\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n\n const vertex = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\n const fragment = `\n#ifdef GL_ES\nprecision mediump float;\n#endif\n\nuniform vec3 iResolution;\nuniform vec2 iMouse;\nuniform float iTime;\n\nuniform float uAngle;\nuniform float uNoise;\nuniform float uBlindCount;\nuniform float uSpotlightRadius;\nuniform float uSpotlightSoftness;\nuniform float uSpotlightOpacity;\nuniform float uMirror;\nuniform float uDistort;\nuniform float uShineFlip;\nuniform vec3 uColor0;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform vec3 uColor4;\nuniform vec3 uColor5;\nuniform vec3 uColor6;\nuniform vec3 uColor7;\nuniform int uColorCount;\n\nvarying vec2 vUv;\n\nfloat rand(vec2 co){\n return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453);\n}\n\nvec2 rotate2D(vec2 p, float a){\n float c = cos(a);\n float s = sin(a);\n return mat2(c, -s, s, c) * p;\n}\n\nvec3 getGradientColor(float t){\n float tt = clamp(t, 0.0, 1.0);\n int count = uColorCount;\n if (count < 2) count = 2;\n float scaled = tt * float(count - 1);\n float seg = floor(scaled);\n float f = fract(scaled);\n\n if (seg < 1.0) return mix(uColor0, uColor1, f);\n if (seg < 2.0 && count > 2) return mix(uColor1, uColor2, f);\n if (seg < 3.0 && count > 3) return mix(uColor2, uColor3, f);\n if (seg < 4.0 && count > 4) return mix(uColor3, uColor4, f);\n if (seg < 5.0 && count > 5) return mix(uColor4, uColor5, f);\n if (seg < 6.0 && count > 6) return mix(uColor5, uColor6, f);\n if (seg < 7.0 && count > 7) return mix(uColor6, uColor7, f);\n if (count > 7) return uColor7;\n if (count > 6) return uColor6;\n if (count > 5) return uColor5;\n if (count > 4) return uColor4;\n if (count > 3) return uColor3;\n if (count > 2) return uColor2;\n return uColor1;\n}\n\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n vec2 uv0 = fragCoord.xy / iResolution.xy;\n\n float aspect = iResolution.x / iResolution.y;\n vec2 p = uv0 * 2.0 - 1.0;\n p.x *= aspect;\n vec2 pr = rotate2D(p, uAngle);\n pr.x /= aspect;\n vec2 uv = pr * 0.5 + 0.5;\n\n vec2 uvMod = uv;\n if (uDistort > 0.0) {\n float a = uvMod.y * 6.0;\n float b = uvMod.x * 6.0;\n float w = 0.01 * uDistort;\n uvMod.x += sin(a) * w;\n uvMod.y += cos(b) * w;\n }\n float t = uvMod.x;\n if (uMirror > 0.5) {\n t = 1.0 - abs(1.0 - 2.0 * fract(t));\n }\n vec3 base = getGradientColor(t);\n\n vec2 offset = vec2(iMouse.x/iResolution.x, iMouse.y/iResolution.y);\n float d = length(uv0 - offset);\n float r = max(uSpotlightRadius, 1e-4);\n float dn = d / r;\n float spot = (1.0 - 2.0 * pow(dn, uSpotlightSoftness)) * uSpotlightOpacity;\n vec3 cir = vec3(spot);\n float stripe = fract(uvMod.x * max(uBlindCount, 1.0));\n if (uShineFlip > 0.5) stripe = 1.0 - stripe;\n vec3 ran = vec3(stripe);\n\n vec3 col = cir + base - ran;\n col += (rand(gl_FragCoord.xy + iTime) - 0.5) * uNoise;\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color;\n mainImage(color, vUv * iResolution.xy);\n gl_FragColor = color;\n}\n`;\n\n const { arr: colorArr, count: colorCount } = prepStops(props.gradientColors);\n const uniforms: {\n iResolution: { value: [number, number, number] };\n iMouse: { value: [number, number] };\n iTime: { value: number };\n uAngle: { value: number };\n uNoise: { value: number };\n uBlindCount: { value: number };\n uSpotlightRadius: { value: number };\n uSpotlightSoftness: { value: number };\n uSpotlightOpacity: { value: number };\n uMirror: { value: number };\n uDistort: { value: number };\n uShineFlip: { value: number };\n uColor0: { value: [number, number, number] };\n uColor1: { value: [number, number, number] };\n uColor2: { value: [number, number, number] };\n uColor3: { value: [number, number, number] };\n uColor4: { value: [number, number, number] };\n uColor5: { value: [number, number, number] };\n uColor6: { value: [number, number, number] };\n uColor7: { value: [number, number, number] };\n uColorCount: { value: number };\n } = {\n iResolution: {\n value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1]\n },\n iMouse: { value: [0, 0] },\n iTime: { value: 0 },\n uAngle: { value: (props.angle * Math.PI) / 180 },\n uNoise: { value: props.noise },\n uBlindCount: { value: Math.max(1, props.blindCount) },\n uSpotlightRadius: { value: props.spotlightRadius },\n uSpotlightSoftness: { value: props.spotlightSoftness },\n uSpotlightOpacity: { value: props.spotlightOpacity },\n uMirror: { value: props.mirrorGradient ? 1 : 0 },\n uDistort: { value: props.distortAmount },\n uShineFlip: { value: props.shineDirection === 'right' ? 1 : 0 },\n uColor0: { value: colorArr[0] },\n uColor1: { value: colorArr[1] },\n uColor2: { value: colorArr[2] },\n uColor3: { value: colorArr[3] },\n uColor4: { value: colorArr[4] },\n uColor5: { value: colorArr[5] },\n uColor6: { value: colorArr[6] },\n uColor7: { value: colorArr[7] },\n uColorCount: { value: colorCount }\n };\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms\n });\n programRef.value = program;\n\n const geometry = new Triangle(gl);\n geometryRef.value = geometry;\n const mesh = new Mesh(gl, { geometry, program });\n meshRef.value = mesh;\n\n const resize = () => {\n const rect = container.getBoundingClientRect();\n renderer.setSize(rect.width, rect.height);\n uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];\n\n if (props.blindMinWidth && props.blindMinWidth > 0) {\n const maxByMinWidth = Math.max(1, Math.floor(rect.width / props.blindMinWidth));\n\n const effective = props.blindCount ? Math.min(props.blindCount, maxByMinWidth) : maxByMinWidth;\n uniforms.uBlindCount.value = Math.max(1, effective);\n } else {\n uniforms.uBlindCount.value = Math.max(1, props.blindCount);\n }\n\n if (firstResizeRef.value) {\n firstResizeRef.value = false;\n const cx = gl.drawingBufferWidth / 2;\n const cy = gl.drawingBufferHeight / 2;\n uniforms.iMouse.value = [cx, cy];\n mouseTargetRef.value = [cx, cy];\n }\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n ro.observe(container);\n\n const onPointerMove = (e: PointerEvent) => {\n const rect = canvas.getBoundingClientRect();\n const scale = (renderer as unknown as { dpr?: number }).dpr || 1;\n const x = (e.clientX - rect.left) * scale;\n const y = (rect.height - (e.clientY - rect.top)) * scale;\n mouseTargetRef.value = [x, y];\n if (props.mouseDampening <= 0) {\n uniforms.iMouse.value = [x, y];\n }\n };\n canvas.addEventListener('pointermove', onPointerMove);\n\n const loop = (t: number) => {\n rafRef.value = requestAnimationFrame(loop);\n uniforms.iTime.value = t * 0.001;\n if (props.mouseDampening > 0) {\n if (!lastTimeRef.value) lastTimeRef.value = t;\n const dt = (t - lastTimeRef.value) / 1000;\n lastTimeRef.value = t;\n const tau = Math.max(1e-4, props.mouseDampening);\n let factor = 1 - Math.exp(-dt / tau);\n if (factor > 1) factor = 1;\n const target = mouseTargetRef.value;\n const cur = uniforms.iMouse.value;\n cur[0] += (target[0] - cur[0]) * factor;\n cur[1] += (target[1] - cur[1]) * factor;\n } else {\n lastTimeRef.value = t;\n }\n if (!props.paused && programRef.value && meshRef.value) {\n try {\n renderer.render({ scene: meshRef.value });\n } catch (e) {\n console.error(e);\n }\n }\n };\n rafRef.value = requestAnimationFrame(loop);\n\n cleanup = () => {\n if (rafRef.value) cancelAnimationFrame(rafRef.value);\n canvas.removeEventListener('pointermove', onPointerMove);\n ro.disconnect();\n if (canvas.parentElement === container) {\n container.removeChild(canvas);\n }\n const callIfFn = <T extends object, K extends keyof T>(obj: T | null, key: K) => {\n if (obj && typeof obj[key] === 'function') {\n (obj[key] as unknown as () => void).call(obj);\n }\n };\n callIfFn(programRef.value, 'remove');\n callIfFn(geometryRef.value, 'remove');\n callIfFn(meshRef.value as unknown as { remove?: () => void }, 'remove');\n callIfFn(rendererRef.value as unknown as { destroy?: () => void }, 'destroy');\n programRef.value = null;\n geometryRef.value = null;\n meshRef.value = null;\n rendererRef.value = null;\n };\n};\n\nonMounted(() => {\n setup();\n});\n\nonBeforeUnmount(() => {\n cleanup?.();\n});\n\nwatch(\n props,\n () => {\n cleanup?.();\n setup();\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n :class=\"['w-full h-full overflow-hidden relative', className]\"\n :style=\"{\n ...(mixBlendMode ? { mixBlendMode: mixBlendMode as CSSProperties['mixBlendMode'] } : {})\n }\"\n />\n</template>\n","path":"GradientBlinds/GradientBlinds.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}],"devDependencies":[],"categories":["Backgrounds"]}