1// DnaHelix â vanilla Three.js port of an R3F particle helix. 2// Renders into a canvas; mouse interaction repels particles which spring back. 3 4function DnaHelix({ 5 color = '#dd3b3c', 6 className = '', 7 style = {} 8}) { 9 const containerRef = React.useRef(null); 10 React.useEffect(() => { 11 const container = containerRef.current; 12 if (!container || !window.THREE) return; 13 const THREE = window.THREE; 14 15 // âââ scene setup âââ 16 const scene = new THREE.Scene(); 17 const renderer = new THREE.WebGLRenderer({ 18 alpha: true, 19 antialias: true 20 }); 21 renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 22 container.appendChild(renderer.domElement); 23 const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 2000); 24 camera.position.set(0, -28, -90); 25 camera.lookAt(0, 0, 0); 26 function resize() { 27 const w = container.clientWidth; 28 const h = container.clientHeight; 29 renderer.setSize(w, h); 30 camera.aspect = w / h; 31 camera.updateProjectionMatrix(); 32 } 33 resize(); 34 const ro = new ResizeObserver(resize); 35 ro.observe(container); 36 37 // âââ geometry: helix + connecting rungs âââ 38 const numHelix = 6000; 39 const numLineSpace = 60; 40 const numLine = 100; 41 const numAmount = numHelix + numLineSpace * numLine; 42 const positions = new Float32Array(numAmount * 3); 43 const radians = new Float32Array(numAmount); 44 const radiuses = new Float32Array(numAmount); 45 const delays = new Float32Array(numAmount); 46 const velocities = new Float32Array(numAmount * 3); 47 for (let i = 0; i < numHelix; i++) { 48 const r = Math.random(); 49 const dx = (Math.random() * 2 - 1) * r * 6; 50 const dy = (Math.random() * 2 - 1) * r * 6; 51 const dz = (Math.random() * 2 - 1) * r * 6; 52 positions[i * 3 + 0] = (i / numHelix * 2 - 1) * 150 + dx; 53 positions[i * 3 + 1] = dy; 54 positions[i * 3 + 2] = dz; 55 radians[i] = THREE.MathUtils.degToRad(i / numHelix * 900 + i % 2 * 180); 56 radiuses[i] = 18; 57 delays[i] = THREE.MathUtils.degToRad(Math.random() * 360); 58 } 59 for (let j = 0; j < numLineSpace; j++) { 60 const radValue = THREE.MathUtils.degToRad(j / numLineSpace * 900); 61 for (let k = 0; k < numLine; k++) { 62 const idx = j * numLine + k + numHelix; 63 const r = Math.random(); 64 const dx = (Math.random() * 2 - 1) * r; 65 const dy = (Math.random() * 2 - 1) * r; 66 const dz = (Math.random() * 2 - 1) * r; 67 positions[idx * 3 + 0] = (j / numLineSpace * 2 - 1) * 150 + dx; 68 positions[idx * 3 + 1] = dy; 69 positions[idx * 3 + 2] = dz; 70 radians[idx] = radValue; 71 radiuses[idx] = (k / numLine * 2 - 1) * 18; 72 delays[idx] = THREE.MathUtils.degToRad(Math.random() * 360); 73 } 74 } 75 const initialPositions = positions.slice(); 76 const geometry = new THREE.BufferGeometry(); 77 geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); 78 geometry.setAttribute('radian', new THREE.BufferAttribute(radians, 1)); 79 geometry.setAttribute('radius', new THREE.BufferAttribute(radiuses, 1)); 80 geometry.setAttribute('delay', new THREE.BufferAttribute(delays, 1)); 81 const uniforms = { 82 time: { 83 value: 0 84 }, 85 colorA: { 86 value: new THREE.Color('#EE3E3E') 87 }, 88 colorB: { 89 value: new THREE.Color('#3772FF') 90 }, 91 mouse: { 92 value: new THREE.Vector3(0, 0, 0) 93 } 94 }; 95 const material = new THREE.ShaderMaterial({ 96 uniforms, 97 transparent: true, 98 depthWrite: false, 99 blending: THREE.NormalBlending, 100 vertexShader: ` 101 attribute float radian; 102 attribute float radius; 103 attribute float delay; 104 uniform float time; 105 varying float vMix; 106 void main() { 107 vec3 updatePosition = position 108 + vec3( 109 sin(-time * 4.0 + delay), 110 sin(radian + -time * 0.4) * (radius + sin(-time * 4.0 + delay)), 111 cos(radian + -time * 0.4) * (radius + sin(-time * 4.0 + delay)) 112 ); 113 vec4 mvPosition = modelViewMatrix * vec4(updatePosition, 1.0
113); 114 float distanceFromCamera = length(mvPosition.xyz); 115 float pointSize = 1000.0 / distanceFromCamera * 0.55; 116 vMix = delay / 6.2831853; 117 gl_Position = projectionMatrix * mvPosition; 118 gl_PointSize = pointSize; 119 } 120 `, 121 fragmentShader: ` 122 precision highp float; 123 uniform vec3 colorA; 124 uniform vec3 colorB; 125 varying float vMix; 126 void main() { 127 vec2 p = gl_PointCoord * 2.0 - 1.0; 128 float r = length(p); 129 float opacity1 = (1.0 - smoothstep(0.5, 0.7, r)); 130 float opacity2 = smoothstep(0.8, 1.0, r) * (1.0 - smoothstep(1.0, 1.2, r)); 131 vec3 col = mix(colorA, colorB, vMix); 132 gl_FragColor = vec4(col, (opacity1 + opacity2) * 0.0633); 133 } 134 ` 135 }); 136 const points = new THREE.Points(geometry, material); 137 scene.add(points); 138 139 // âââ mouse interaction âââ 140 const mouseNDC = new THREE.Vector2(0, 0); 141 let interactionStarted = false; 142 function onMove(e) { 143 const rect = container.getBoundingClientRect(); 144 mouseNDC.x = (e.clientX - rect.left) / rect.width * 2 - 1; 145 mouseNDC.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; 146 interactionStarted = true; 147 } 148 window.addEventListener('mousemove', onMove); 149 const raycaster = new THREE.Raycaster(); 150 const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); 151 const intersection = new THREE.Vector3(); 152 function smoother(t) { 153 return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2; 154 } 155 156 // âââ animation loop âââ 157 const clock = new THREE.Clock(); 158 let raf = 0; 159 let running = true; 160 const positionsAttr = geometry.attributes.position.array; 161 const repellingRadius = 15.0; 162 const velocityFactor = 1.0; 163 function tick() { 164 if (!running) return; 165 uniforms.time.value = clock.getElapsedTime(); 166 if (interactionStarted) { 167 raycaster.setFromCamera(mouseNDC, camera); 168 raycaster.ray.intersectPlane(plane, intersection); 169 for (let i = 0; i < positionsAttr.length; i += 3) { 170 const px = positionsAttr[i]; 171 const py = positionsAttr[i + 1]; 172 const pz = positionsAttr[i + 2]; 173 const dxx = px - intersection.x; 174 const dyy = py - intersection.y; 175 const dzz = pz - intersection.z; 176 const dist = Math.sqrt(dxx * dxx + dyy * dyy + dzz * dzz) || 1e-6; 177 if (dist < repellingRadius) { 178 const dirX = dxx / dist; 179 const dirY = dyy / dist; 180 const dirZ = dzz / dist; 181 const ease = smoother(1 - dist / repellingRadius); 182 const strength = ease * velocityFactor; 183 velocities[i] += dirX * strength; 184 velocities[i + 1] += dirY * strength; 185 velocities[i + 2] += dirZ * strength; 186 } 187 positionsAttr[i] += velocities[i]; 188 positionsAttr[i + 1] += velocities[i + 1]; 189 positionsAttr[i + 2] += velocities[i + 2]; 190 velocities[i] *= 0.95; 191 velocities[i + 1] *= 0.95; 192 velocities[i + 2] *= 0.95; 193 positionsAttr[i] += (initialPositions[i] - positionsAttr[i]) * 0.09; 194 positionsAttr[i + 1] += (initialPositions[i + 1] - positionsAttr[i + 1]) * 0.09; 195 positionsAttr[i + 2] += (initialPositions[i + 2] - positionsAttr[i + 2]) * 0.09; 196 } 197 geometry.attributes.position.needsUpdate = true; 198 } 199 renderer.render(scene, camera); 200 raf = requestAnimationFrame(tick); 201 } 202 tick(); 203 return () => { 204 running = false; 205 cancelAnimationFrame(raf); 206 ro.disconnect(); 207 window.removeEventListener('mousemove', onMove); 208 geometry.dispose(); 209 material.dispose(); 210 renderer.dispose(); 211 if (renderer.domElement.parentNode) { 212 renderer.domElement.parentNode.removeChild(renderer.domElement); 213 } 214 }; 215 }, [color]); 216 return /*#__PURE__*/React.createElement("div", { 217 ref: containerRef, 218 className: className, 219 style: { 220 position: 'absolute', 221 inset: 0, 222 ...style 223 }, 224 "aria-hidden": "true" 225 }); 226} 227window.DnaHelix = DnaHelix;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.