Files
Utkarsh-Singhal-26 88b0150b23 🎉 New <MagicRings /> component
2026-03-12 16:08:54 +05:30

1 line
9.5 KiB
JSON

{"name":"MagicRings","title":"MagicRings","description":"Interactive magic rings effect with customizable parameters.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport * as THREE from 'three';\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float uTime, uAttenuation, uLineThickness;\nuniform float uBaseRadius, uRadiusStep, uScaleRate;\nuniform float uOpacity, uNoiseAmount, uRotation, uRingGap;\nuniform float uFadeIn, uFadeOut;\nuniform float uMouseInfluence, uHoverAmount, uHoverScale, uParallax, uBurst;\nuniform vec2 uResolution, uMouse;\nuniform vec3 uColor, uColorTwo;\nuniform int uRingCount;\n\nconst float HP = 1.5707963;\nconst float CYCLE = 3.45;\n\nfloat fade(float t) {\n return t < uFadeIn ? smoothstep(0.0, uFadeIn, t) : 1.0 - smoothstep(uFadeOut, CYCLE - 0.2, t);\n}\n\nfloat ring(vec2 p, float ri, float cut, float t0, float px) {\n float t = mod(uTime + t0, CYCLE);\n float r = ri + t / CYCLE * uScaleRate;\n float d = abs(length(p) - r);\n float a = atan(abs(p.y), abs(p.x)) / HP;\n float th = max(1.0 - a, 0.5) * px * uLineThickness;\n float h = (1.0 - smoothstep(th, th * 1.5, d)) + 1.0;\n d += pow(cut * a, 3.0) * r;\n return h * exp(-uAttenuation * d) * fade(t);\n}\n\nvoid main() {\n float px = 1.0 / min(uResolution.x, uResolution.y);\n vec2 p = (gl_FragCoord.xy - 0.5 * uResolution.xy) * px;\n float cr = cos(uRotation), sr = sin(uRotation);\n p = mat2(cr, -sr, sr, cr) * p;\n p -= uMouse * uMouseInfluence;\n float sc = mix(1.0, uHoverScale, uHoverAmount) + uBurst * 0.3;\n p /= sc;\n vec3 c = vec3(0.0);\n float rcf = max(float(uRingCount) - 1.0, 1.0);\n for (int i = 0; i < 10; i++) {\n if (i >= uRingCount) break;\n float fi = float(i);\n vec2 pr = p - fi * uParallax * uMouse;\n vec3 rc = mix(uColor, uColorTwo, fi / rcf);\n c = mix(c, rc, vec3(ring(pr, uBaseRadius + fi * uRadiusStep, pow(uRingGap, fi), i == 0 ? 0.0 : 2.95 * fi, px)));\n }\n c *= 1.0 + uBurst * 2.0;\n float n = fract(sin(dot(gl_FragCoord.xy + uTime * 100.0, vec2(12.9898, 78.233))) * 43758.5453);\n c += (n - 0.5) * uNoiseAmount;\n gl_FragColor = vec4(c, max(c.r, max(c.g, c.b)) * uOpacity);\n}\n`;\n\ninterface MagicRingsProps {\n color?: string;\n colorTwo?: string;\n speed?: number;\n ringCount?: number;\n attenuation?: number;\n lineThickness?: number;\n baseRadius?: number;\n radiusStep?: number;\n scaleRate?: number;\n opacity?: number;\n blur?: number;\n noiseAmount?: number;\n rotation?: number;\n ringGap?: number;\n fadeIn?: number;\n fadeOut?: number;\n followMouse?: boolean;\n mouseInfluence?: number;\n hoverScale?: number;\n parallax?: number;\n clickBurst?: boolean;\n}\n\nconst props = withDefaults(defineProps<MagicRingsProps>(), {\n color: '#7cff67',\n colorTwo: '#42fcff',\n speed: 1,\n ringCount: 6,\n attenuation: 10,\n lineThickness: 2,\n baseRadius: 0.35,\n radiusStep: 0.1,\n scaleRate: 0.1,\n opacity: 1,\n blur: 0,\n noiseAmount: 0.1,\n rotation: 0,\n ringGap: 1.5,\n fadeIn: 0.7,\n fadeOut: 0.5,\n followMouse: false,\n mouseInfluence: 0.2,\n hoverScale: 1.2,\n parallax: 0.05,\n clickBurst: false\n});\n\nconst mountRef = useTemplateRef('mountRef');\n\nconst mouseRef = ref<[number, number]>([0, 0]);\nconst smoothMouseRef = ref<[number, number]>([0, 0]);\nconst hoverAmountRef = ref(0);\nconst isHoveredRef = ref(false);\nconst burstRef = ref(0);\n\nconst propsRef = computed(() => ({\n color: props.color,\n colorTwo: props.colorTwo,\n speed: props.speed,\n ringCount: props.ringCount,\n attenuation: props.attenuation,\n lineThickness: props.lineThickness,\n baseRadius: props.baseRadius,\n radiusStep: props.radiusStep,\n scaleRate: props.scaleRate,\n opacity: props.opacity,\n blur: props.blur,\n noiseAmount: props.noiseAmount,\n rotation: props.rotation,\n ringGap: props.ringGap,\n fadeIn: props.fadeIn,\n fadeOut: props.fadeOut,\n followMouse: props.followMouse,\n mouseInfluence: props.mouseInfluence,\n hoverScale: props.hoverScale,\n parallax: props.parallax,\n clickBurst: props.clickBurst\n}));\n\nlet renderer: THREE.WebGLRenderer | null = null;\nlet frameId = 0;\nlet ro: ResizeObserver | null = null;\n\nconst cleanupFns: (() => void)[] = [];\nonMounted(() => {\n const mount = mountRef.value;\n if (!mount) return;\n\n try {\n renderer = new THREE.WebGLRenderer({ alpha: true });\n } catch {\n return;\n }\n\n if (!renderer.capabilities.isWebGL2) {\n renderer.dispose();\n return;\n }\n\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n\n const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);\n camera.position.z = 1;\n\n const uniforms = {\n uTime: { value: 0 },\n uAttenuation: { value: 0 },\n uResolution: { value: new THREE.Vector2() },\n uColor: { value: new THREE.Color() },\n uColorTwo: { value: new THREE.Color() },\n uLineThickness: { value: 0 },\n uBaseRadius: { value: 0 },\n uRadiusStep: { value: 0 },\n uScaleRate: { value: 0 },\n uRingCount: { value: 0 },\n uOpacity: { value: 1 },\n uNoiseAmount: { value: 0 },\n uRotation: { value: 0 },\n uRingGap: { value: 1.6 },\n uFadeIn: { value: 0.5 },\n uFadeOut: { value: 0.75 },\n uMouse: { value: new THREE.Vector2() },\n uMouseInfluence: { value: 0 },\n uHoverAmount: { value: 0 },\n uHoverScale: { value: 1 },\n uParallax: { value: 0 },\n uBurst: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms,\n transparent: true\n });\n\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n scene.add(quad);\n\n const resize = () => {\n const w = mount.clientWidth;\n const h = mount.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer!.setSize(w, h);\n renderer!.setPixelRatio(dpr);\n\n uniforms.uResolution.value.set(w * dpr, h * dpr);\n };\n\n resize();\n\n window.addEventListener('resize', resize);\n\n ro = new ResizeObserver(resize);\n ro.observe(mount);\n\n const onMouseMove = (e: MouseEvent) => {\n const rect = mount.getBoundingClientRect();\n\n mouseRef.value[0] = (e.clientX - rect.left) / rect.width - 0.5;\n mouseRef.value[1] = -((e.clientY - rect.top) / rect.height - 0.5);\n };\n\n const onMouseEnter = () => {\n isHoveredRef.value = true;\n };\n\n const onMouseLeave = () => {\n isHoveredRef.value = false;\n mouseRef.value = [0, 0];\n };\n\n const onClick = () => {\n burstRef.value = 1;\n };\n\n mount.addEventListener('mousemove', onMouseMove);\n mount.addEventListener('mouseenter', onMouseEnter);\n mount.addEventListener('mouseleave', onMouseLeave);\n mount.addEventListener('click', onClick);\n\n const animate = (t: number) => {\n frameId = requestAnimationFrame(animate);\n\n const p = propsRef.value;\n\n smoothMouseRef.value[0] += (mouseRef.value[0] - smoothMouseRef.value[0]) * 0.08;\n smoothMouseRef.value[1] += (mouseRef.value[1] - smoothMouseRef.value[1]) * 0.08;\n\n hoverAmountRef.value += ((isHoveredRef.value ? 1 : 0) - hoverAmountRef.value) * 0.08;\n\n burstRef.value *= 0.95;\n if (burstRef.value < 0.001) burstRef.value = 0;\n\n uniforms.uTime.value = t * 0.001 * p.speed;\n uniforms.uAttenuation.value = p.attenuation;\n\n uniforms.uColor.value.set(p.color);\n uniforms.uColorTwo.value.set(p.colorTwo);\n\n uniforms.uLineThickness.value = p.lineThickness;\n uniforms.uBaseRadius.value = p.baseRadius;\n uniforms.uRadiusStep.value = p.radiusStep;\n uniforms.uScaleRate.value = p.scaleRate;\n\n uniforms.uRingCount.value = p.ringCount;\n\n uniforms.uOpacity.value = p.opacity;\n uniforms.uNoiseAmount.value = p.noiseAmount;\n\n uniforms.uRotation.value = (p.rotation * Math.PI) / 180;\n\n uniforms.uRingGap.value = p.ringGap;\n uniforms.uFadeIn.value = p.fadeIn;\n uniforms.uFadeOut.value = p.fadeOut;\n\n uniforms.uMouse.value.set(smoothMouseRef.value[0], smoothMouseRef.value[1]);\n\n uniforms.uMouseInfluence.value = p.followMouse ? p.mouseInfluence : 0;\n\n uniforms.uHoverAmount.value = hoverAmountRef.value;\n uniforms.uHoverScale.value = p.hoverScale;\n\n uniforms.uParallax.value = p.parallax;\n uniforms.uBurst.value = p.clickBurst ? burstRef.value : 0;\n\n renderer!.render(scene, camera);\n };\n\n frameId = requestAnimationFrame(animate);\n\n cleanupFns.push(() => {\n cancelAnimationFrame(frameId);\n\n window.removeEventListener('resize', resize);\n\n ro?.disconnect();\n\n mount.removeEventListener('mousemove', onMouseMove);\n mount.removeEventListener('mouseenter', onMouseEnter);\n mount.removeEventListener('mouseleave', onMouseLeave);\n mount.removeEventListener('click', onClick);\n\n mount.removeChild(renderer!.domElement);\n\n renderer?.dispose();\n material.dispose();\n });\n});\n\nonBeforeUnmount(() => {\n cleanupFns.forEach(fn => fn());\n});\n</script>\n\n<template>\n <div ref=\"mountRef\" class=\"w-full h-full\" :style=\"props.blur > 0 ? { filter: `blur(${props.blur}px)` } : undefined\" />\n</template>\n","path":"MagicRings/MagicRings.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Animations"]}