mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
10 KiB
JSON
1 line
10 KiB
JSON
{"name":"ColorBends","title":"ColorBends","description":"Vibrant color bends with smooth flowing animation.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport * as THREE from 'three';\nimport { onMounted, onBeforeUnmount, watch, ref, useTemplateRef, type CSSProperties } from 'vue';\n\ntype ColorBendsProps = {\n className?: string;\n style?: CSSProperties;\n rotation?: number;\n speed?: number;\n colors?: string[];\n transparent?: boolean;\n autoRotate?: number;\n scale?: number;\n frequency?: number;\n warpStrength?: number;\n mouseInfluence?: number;\n parallax?: number;\n noise?: number;\n};\n\nconst MAX_COLORS = 8 as const;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst props = withDefaults(defineProps<ColorBendsProps>(), {\n rotation: 45,\n speed: 0.2,\n colors: () => [],\n transparent: true,\n autoRotate: 0,\n scale: 1,\n frequency: 1,\n warpStrength: 1,\n mouseInfluence: 1,\n parallax: 0.5,\n noise: 0.1\n});\n\nconst containerRef = useTemplateRef('containerRef');\nconst rendererRef = ref<THREE.WebGLRenderer | null>(null);\nconst rafRef = ref<number | null>(null);\nconst materialRef = ref<THREE.ShaderMaterial | null>(null);\nconst resizeObserverRef = ref<ResizeObserver | null>(null);\nconst rotationRef = ref(props.rotation);\nconst autoRotateRef = ref(props.autoRotate);\nconst pointerTargetRef = ref(new THREE.Vector2(0, 0));\nconst pointerCurrentRef = ref(new THREE.Vector2(0, 0));\nconst pointerSmoothRef = ref(8);\n\nlet cleanup: (() => void) | null = null;\n\nconst setup = () => {\n const container = containerRef.value!;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: props.speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: props.transparent ? 1 : 0 },\n uScale: { value: props.scale },\n uFrequency: { value: props.frequency },\n uWarpStrength: { value: props.warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: props.mouseInfluence },\n uParallax: { value: props.parallax },\n uNoise: { value: props.noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.value = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.value = renderer;\n (renderer as any).outputColorSpace = (THREE as any).SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, props.transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n (material.uniforms.uCanvas.value as THREE.Vector2).set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.value = ro;\n } else {\n (window as any).addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.value % 360) + autoRotateRef.value * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n (material.uniforms.uRot.value as THREE.Vector2).set(c, s);\n\n const cur = pointerCurrentRef.value;\n const tgt = pointerTargetRef.value;\n const amt = Math.min(1, dt * pointerSmoothRef.value);\n cur.lerp(tgt, amt);\n (material.uniforms.uPointer.value as THREE.Vector2).copy(cur);\n renderer.render(scene, camera);\n rafRef.value = requestAnimationFrame(loop);\n };\n rafRef.value = requestAnimationFrame(loop);\n\n const handlePointerMove = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n pointerTargetRef.value.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n\n cleanup = () => {\n if (rafRef.value !== null) cancelAnimationFrame(rafRef.value);\n if (resizeObserverRef.value) resizeObserverRef.value.disconnect();\n else window.removeEventListener('resize', handleResize);\n container.removeEventListener('pointermove', handlePointerMove);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n};\n\nonMounted(setup);\nonBeforeUnmount(() => cleanup?.());\n\nwatch(\n () => ({ ...props }),\n () => {\n const material = materialRef.value;\n const renderer = rendererRef.value;\n if (!material) return;\n\n rotationRef.value = props.rotation;\n autoRotateRef.value = props.autoRotate;\n material.uniforms.uSpeed.value = props.speed;\n material.uniforms.uScale.value = props.scale;\n material.uniforms.uFrequency.value = props.frequency;\n material.uniforms.uWarpStrength.value = props.warpStrength;\n material.uniforms.uMouseInfluence.value = props.mouseInfluence;\n material.uniforms.uParallax.value = props.parallax;\n material.uniforms.uNoise.value = props.noise;\n\n const toVec3 = (hex: string) => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (props.colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = (material.uniforms.uColors.value as THREE.Vector3[])[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = props.transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, props.transparent ? 0 : 1);\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div ref=\"containerRef\" :class=\"['w-full h-full relative overflow-hidden', props.className]\" :style=\"props.style\" />\n</template>\n","path":"ColorBends/ColorBends.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Backgrounds"]} |