let rgbString = 'rgba(0,235,136,' ; let sphereRad = 280 ; let radius_sp = 1 ; const wait = 1 ; const numToAddEachFrame = 7 ; const particleAlpha = 1 ; const fLen = 320 ; const zMax = fLen - 2 ; function getProjCenterX ( displayWidth ) { if ( displayWidth < 1280 ) return Math . round ( displayWidth / 2 ) ; return Math . round ( displayWidth * 0.7 ) ; } const canvasEffect = ( ) => { const canvas = document . querySelector ( '#particles-canvas' ) as HTMLCanvasElement ; const context = canvas . getContext ( '2d' , { alpha : false } ) ; let displayWidth = canvas . width ; let displayHeight = canvas . height ; let projCenterX = getProjCenterX ( displayWidth ) ; let projCenterY = displayHeight / 2 ; const timers = [ ] ; let direction = 'top' ; let count ; let particleList ; let recycleBin ; let m ; let turnAngle ; let turnSpeed ; let sphereCenterX , sphereCenterY , sphereCenterZ ; let particleRad ; let zeroAlphaDepth ; let randAccelX , randAccelY , randAccelZ ; let gravity ; let p ; let outsideTest ; let nextParticle ; let sinAngle ; let cosAngle ; let rotX , rotZ ; let depthAlphaFactor ; let i ; let theta , phi ; let x0 , y0 , z0 ; const requestAnimFrame = ( ( ) => { return ( window . requestAnimationFrame || function ( callback ) { window . setTimeout ( callback , 1000 / 60 ) ; } ) ; } ) ( ) ; const requestInterval = ( fn , delay ) => { var start = new Date ( ) . getTime ( ) , handle = { } ; function loop ( ) { handle . value = requestAnimFrame ( loop ) ; var current = new Date ( ) . getTime ( ) , delta = current - start ; if ( delta >= delay ) { fn . call ( ) ; start = new Date ( ) . getTime ( ) ; } } handle . value = requestAnimFrame ( loop ) ; return handle ; } ; const handleDirection = ( ) => { if ( direction === 'top' ) { sphereRad += 5 ; if ( sphereRad > 600 ) direction = 'bottom' ; } else { sphereRad -= 5 ; if ( sphereRad < 50 ) direction = 'top' ; } } ; const loop = ( p ) => { nextParticle = p . next ; p . age ++ ; if ( p . age > p . stuckTime ) { p . velX += p . accelX + randAccelX * ( Math . random ( ) * 2 - 1 ) ; p . velY += p . accelY + randAccelY * ( Math . random ( ) * 2 - 1 ) ; p . velZ += p . accelZ + randAccelZ * ( Math . random ( ) * 2 - 1 ) ; p . x += p . velX ; p . y += p . velY ; p . z += p . velZ ; } rotX = cosAngle * p . x + sinAngle * ( p . z - sphereCenterZ ) ; rotZ = - sinAngle * p . x + cosAngle * ( p . z - sphereCenterZ ) + sphereCenterZ ; m = ( radius_sp * fLen ) / ( fLen - rotZ ) ; p . projX = rotX * m + projCenterX ; p . projY = p . y * m + projCenterY ; if ( p . age < p . attack + p . hold + p . decay ) { if ( p . age < p . attack ) { p . alpha = ( ( p . holdValue - p . initValue ) / p . attack ) * p . age + p . initValue ; } else if ( p . age < p . attack + p . hold ) { p . alpha = p . holdValue ; } else if ( p . age < p . attack + p . hold + p . decay ) { p . alpha = ( ( p . lastValue - p . holdValue ) / p . decay ) * ( p . age - p . attack - p . hold ) + p . holdValue ; } } else { p . dead = true ; } if ( p . projX > displayWidth || p . projX < 0 || p . projY < 0 || p . projY > displayHeight || rotZ > zMax ) { outsideTest = true ; } else { outsideTest = false ; } if ( outsideTest || p . dead ) { recycle ( p ) ; p = null ; } else { depthAlphaFactor = 1 - rotZ / zeroAlphaDepth ; depthAlphaFactor = depthAlphaFactor > 1 ? 1 : depthAlphaFactor < 0 ? 0 : depthAlphaFactor ; context . fillStyle = rgbString + depthAlphaFactor * p . alpha + ')' ; context . beginPath ( ) ; context . arc ( p . projX , p . projY , m * particleRad , 0 , 2 * Math . PI , false ) ; context . closePath ( ) ; context . fill ( ) ; } return ( p = nextParticle ) ; } ; const tick = ( ) => { count ++ ; if ( count >= wait ) { count = 0 ; for ( i = 0 ; i < numToAddEachFrame ; i ++ ) { theta = Math . random ( ) * 2 * Math . PI ; phi = Math . acos ( Math . random ( ) * 2 - 1 ) ; x0 = sphereRad * Math . sin ( phi ) * Math . cos ( theta ) ; y0 = sphereRad * Math . sin ( phi ) * Math . sin ( theta ) ; z0 = sphereRad * Math . cos ( phi ) ; var p = addParticle ( x0 , sphereCenterY + y0 , sphereCenterZ + z0 , 0.002 * x0 , 0.002 * y0 , 0.002 * z0 ) ; p . attack = 50 ; p . hold = 50 ; p . decay = 100 ; p . initValue = 0 ; p . holdValue = particleAlpha ; p . lastValue = 0 ; p . stuckTime = 90 + Math . random ( ) * 20 ; p . accelX = 0 ; p . accelY = gravity ; p . accelZ = 0 ; } } turnAngle = ( turnAngle + turnSpeed ) % ( 2 * Math . PI ) ; sinAngle = Math . sin ( turnAngle ) ; cosAngle = Math . cos ( turnAngle ) ; context . fillStyle = '#000000' ; context . fillRect ( 0 , 0 , displayWidth , displayHeight ) ; p = particleList . first ; while ( p != null ) { p = loop ( p ) ; } } ; const addParticle = ( x0 , y0 , z0 , vx0 , vy0 , vz0 ) => { var newParticle ; var color ; if ( recycleBin . first != null ) { newParticle = recycleBin . first ; if ( newParticle . next != null ) { recycleBin . first = newParticle . next ; newParticle . next . prev = null ; } else { recycleBin . first = null ; } } else { newParticle = { } ; } if ( particleList . first == null ) { particleList . first = newParticle ; newParticle . prev = null ; newParticle . next = null ; } else { newParticle . next = particleList . first ; particleList . first . prev = newParticle ; particleList . first = newParticle ; newParticle . prev = null ; } newParticle . x = x0 ; newParticle . y = y0 ; newParticle . z = z0 ; newParticle . velX = vx0 ; newParticle . velY = vy0 ; newParticle . velZ = vz0 ; newParticle . age = 0 ; newParticle . dead = false ; if ( Math . random ( ) < 0.5 ) { newParticle . right = true ; } else { newParticle . right = false ; } return newParticle ; } ; const recycle = ( p ) => { if ( particleList . first == p ) { if ( p . next != null ) { p . next . prev = null ; particleList . first = p . next ; } else { particleList . first = null ; } } else { if ( p . next == null ) { p . prev . next = null ; } else { p . prev . next = p . next ; p . next . prev = p . prev ; } } if ( recycleBin . first == null ) { recycleBin . first = p ; p . prev = null ; p . next = null ; } else { p . next = recycleBin . first ; recycleBin . first . prev = p ; recycleBin . first = p ; p . prev = null ; } } ; const setError = ( ) => { rgbString = 'rgba(255,0,0,' ; } ; const clearError = ( ) => { rgbString = 'rgba(0,235,136,' ; } ; const onUnmount = ( ) => { timers . forEach ( ( timer ) => window . clearInterval ( timer ) ) ; } ; const onResize = ( ) => { displayWidth = canvas . width ; displayHeight = canvas . height ; projCenterY = displayHeight / 2 ; projCenterX = getProjCenterX ( displayWidth ) ; } ; const init = ( ) => { count = wait - 1 ; particleList = { } ; recycleBin = { } ; randAccelX = 0.1 ; randAccelY = 0.1 ; randAccelZ = 0.1 ; gravity = - 0 ; particleRad = 2.5 ; sphereCenterX = 0 ; sphereCenterY = 0 ; sphereCenterZ = - 3 - sphereRad ; zeroAlphaDepth = - 750 ; turnSpeed = ( 2 * Math . PI ) / 1200 ; turnAngle = 0 ; } ; init ( ) ; requestInterval ( tick , 10 / 24 ) ; requestInterval ( handleDirection , 500 ) ; return { setError , clearError , onUnmount , onResize } ; } ; export default canvasEffect ;