-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathFloatingLines-JS-TW.json
More file actions
18 lines (18 loc) · 14.2 KB
/
FloatingLines-JS-TW.json
File metadata and controls
18 lines (18 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "FloatingLines-JS-TW",
"title": "FloatingLines",
"description": "3D floating lines that react to cursor movement.",
"type": "registry:component",
"files": [
{
"type": "registry:component",
"path": "FloatingLines/FloatingLines.jsx",
"content": "import { useEffect, useRef } from 'react';\nimport {\n Clock,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = waveType => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = waveType => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let active = true;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n if (!active) return;\n const width = container.clientWidth || 1;\n const height = container.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => {\n if (!active) return;\n setSize();\n })\n : null;\n\n if (ro) ro.observe(container);\n\n const handlePointerMove = event => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n if (!active) return;\n\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n active = false;\n\n cancelAnimationFrame(raf);\n\n if (ro) ro.disconnect();\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n <div\n ref={containerRef}\n className=\"relative w-full h-full overflow-hidden floating-lines-container\"\n style={{\n mixBlendMode: mixBlendMode\n }}\n />\n );\n}\n"
}
],
"registryDependencies": [],
"dependencies": [
"three@^0.167.1"
]
}