A mouse sensitive canvas/audio-visualizer experiment in pure JS
A Pen by BergenBergen BergenBergen on CodePen.
A mouse sensitive canvas/audio-visualizer experiment in pure JS
A Pen by BergenBergen BergenBergen on CodePen.
| <a id="btStartAudioVisualization" class="bt">Start Audio Visualization</a> | |
| <p id="txtStatus"></p> |
| /* | |
| Song: LAKEY INSPIRED - Chill Day (Vlog No Copyright Music) Music provided by Vlog No Copyright Music. Video Link: https://youtu.be/vtHGESuQ22s | |
| */ | |
| //--- | |
| var audio, audioContext, audioSrc; | |
| var analyser, analyserBufferLength; | |
| //--- | |
| var w; | |
| var h; | |
| var btStart; | |
| var txtStatus; | |
| var canvas; | |
| var context; | |
| var imageData; | |
| var data; | |
| var mouseActive = false; | |
| var mouseDown = false; | |
| var mousePos = { x:0, y:0 }; | |
| var mouseFollowSpeed = 0.015; | |
| var fov = 250; | |
| var speed = 0.75; | |
| var particles = []; | |
| var particlesCenter = []; | |
| var time = 0; | |
| var colorInvertValue = 0; | |
| //--- | |
| function init() { | |
| canvas = document.createElement( 'canvas' ); | |
| canvas.addEventListener( 'mousedown', mouseDownHandler, false ); | |
| canvas.addEventListener( 'mouseup', mouseUpHandler, false ); | |
| canvas.addEventListener( 'mousemove', mouseMoveHandler, false ); | |
| canvas.addEventListener( 'mouseenter', mouseEnterHandler, false ); | |
| canvas.addEventListener( 'mouseleave', mouseLeaveHandler, false ); | |
| document.body.appendChild( canvas ); | |
| context = canvas.getContext( '2d' ); | |
| window.addEventListener( 'resize', onResize, false ); | |
| onResize(); | |
| addParticles(); | |
| render(); | |
| clearImageData(); | |
| render(); | |
| context.putImageData( imageData, 0, 0 ); | |
| btStart = document.getElementById( 'btStartAudioVisualization' ); | |
| btStart.addEventListener( 'mousedown', userStart, false ); | |
| txtStatus = document.getElementById( 'txtStatus' ); | |
| txtStatus.innerHTML = 'Waiting Patiently For You... Please Click the Start Button.'; | |
| }; | |
| //--- | |
| function userStart() { | |
| btStart.removeEventListener( 'mousedown', userStart ); | |
| btStart.addEventListener( 'mousedown', audioBtHandler, false ); | |
| btStart.innerHTML = 'Pause Audio'; | |
| txtStatus.innerHTML = 'Loading Audio...'; | |
| audioSetup(); | |
| animate(); | |
| }; | |
| //--- | |
| function audioSetup() { | |
| audio = new Audio(); | |
| audio.src = 'http://nkunited.de/ExternalImages/jsfiddle/audio/ChillDay_comp.mp3'; | |
| audio.controls = false; | |
| audio.loop = true; | |
| audio.autoplay = true; | |
| audio.crossOrigin = 'anonymous'; | |
| audio.addEventListener( 'canplaythrough', audioLoaded, false ); | |
| audioContext = new ( window.AudioContext || window.webkitAudioContext )(); | |
| analyser = audioContext.createAnalyser(); | |
| analyser.connect( audioContext.destination ); | |
| analyser.smoothingTimeConstant = 0.65; | |
| analyser.fftSize = 512 * 32;//circleSegments * 32; | |
| analyserBufferLength = analyser.frequencyBinCount; | |
| audioSrc = audioContext.createMediaElementSource( audio ); | |
| audioSrc.connect( analyser ); | |
| }; | |
| function audioLoaded( event ) { | |
| txtStatus.innerHTML = 'Song: LAKEY INSPIRED - Chill Day'; | |
| //txtStatus.style.display = 'none'; | |
| }; | |
| //--- | |
| function clearImageData() { | |
| for ( var i = 0, l = data.length; i < l; i += 4 ) { | |
| data[ i ] = 0; | |
| data[ i + 1 ] = 0; | |
| data[ i + 2 ] = 0; | |
| data[ i + 3 ] = 255; | |
| } | |
| }; | |
| function setPixel( x, y, r, g, b, a ) { | |
| var i = ( x + y * imageData.width ) * 4; | |
| data[ i ] = r; | |
| data[ i + 1 ] = g; | |
| data[ i + 2 ] = b; | |
| data[ i + 3 ] = a; | |
| }; | |
| //--- | |
| function drawLine( x1, y1, x2, y2, r, g, b, a ) { | |
| var dx = Math.abs( x2 - x1 ); | |
| var dy = Math.abs( y2 - y1 ); | |
| var sx = ( x1 < x2 ) ? 1 : -1; | |
| var sy = ( y1 < y2 ) ? 1 : -1; | |
| var err = dx - dy; | |
| var lx = x1; | |
| var ly = y1; | |
| while ( true ) { | |
| if ( lx > 0 && lx < w && ly > 0 && ly < h ) { | |
| setPixel( lx, ly, r, g, b, a ); | |
| } | |
| if ( ( lx === x2 ) && ( ly === y2 ) ) | |
| break; | |
| var e2 = 2 * err; | |
| if ( e2 > -dx ) { | |
| err -= dy; | |
| lx += sx; | |
| } | |
| if ( e2 < dy ) { | |
| err += dx; | |
| ly += sy; | |
| } | |
| } | |
| }; | |
| //--- | |
| function getCirclePosition( centerX, centerY, radius, index, segments ) { | |
| var angle = index * ( ( Math.PI * 2 ) / segments ) + time; | |
| var x = centerX + Math.cos( angle ) * radius; | |
| var y = centerY + Math.sin( angle ) * radius; | |
| return { x:x, y:y }; | |
| }; | |
| function drawCircle( centerPosition, radius, segments ) { | |
| var coordinates = []; | |
| var radiusSave; | |
| var diff = 0;//Math.floor( Math.random() * segments ); | |
| for ( var i = 0; i <= segments; i++ ) { | |
| //var radiusRandom = radius + Math.random() * ( radius / 8 ); | |
| //var radiusRandom = radius + Math.random() * ( radius / 32 ); | |
| var radiusRandom = radius;// + ( radius / 8 ); | |
| if ( i === 0 ) { | |
| radiusSave = radiusRandom; | |
| } | |
| if ( i === segments ) { | |
| radiusRandom = radiusSave; | |
| } | |
| var centerX = centerPosition.x; | |
| var centerY = centerPosition.y; | |
| var position = getCirclePosition( centerX, centerY, radiusRandom, i, segments ); | |
| coordinates.push( { x:position.x, y:position.y, index:i + diff, radius:radiusRandom, segments:segments, centerX:centerX, centerY:centerY } ); | |
| } | |
| return coordinates; | |
| }; | |
| //--- | |
| function addParticle( x, y, z, audioBufferIndex ) { | |
| var particle = {}; | |
| particle.x = x; | |
| particle.y = y; | |
| particle.z = z; | |
| particle.x2d = 0; | |
| particle.y2d = 0; | |
| particle.audioBufferIndex = audioBufferIndex; | |
| return particle; | |
| }; | |
| function addParticles() { | |
| var audioBufferIndexMin = 8; | |
| var audioBufferIndexMax = 1024; | |
| var audioBufferIndex = audioBufferIndexMin; | |
| var centerPosition = { x:0, y:0 }; | |
| var center = { x:0, y:0 }; | |
| var c = 0; | |
| var w1 = Math.random() * ( w / 1 ); | |
| var h1 = Math.random() * ( h / 1 ); | |
| for ( var z = -fov; z < fov; z += 4 ) { | |
| var coordinates = drawCircle( centerPosition, 75, 64 ); | |
| var particlesRow = []; | |
| center.x = ( ( w / 2 ) - w1 ) * ( c / 15 ) + w / 2; | |
| center.y = ( ( h / 2 ) - h1 ) * ( c / 15 ) + w / 2; | |
| c++; | |
| //var center = { x:w / 2, y:h / 2 }; | |
| particlesCenter.push( center ); | |
| audioBufferIndex = Math.floor( Math.random() * audioBufferIndexMax ) + audioBufferIndexMin; | |
| for ( var i = 0, l = coordinates.length; i < l; i++ ) { | |
| var coordinate = coordinates[ i ]; | |
| var particle = addParticle( coordinate.x, coordinate.y, z, audioBufferIndex ); | |
| particle.index = coordinate.index; | |
| particle.radius = coordinate.radius; | |
| particle.radiusAudio = particle.radius; | |
| particle.segments = coordinate.segments; | |
| particle.centerX = coordinate.centerX; | |
| particle.centerY = coordinate.centerY; | |
| particlesRow.push( particle ); | |
| if ( i < coordinates.length / 2 ) { | |
| audioBufferIndex++; | |
| } else { | |
| audioBufferIndex--; | |
| } | |
| if ( audioBufferIndex > audioBufferIndexMax ) { | |
| audioBufferIndex = audioBufferIndexMin; | |
| } | |
| if ( audioBufferIndex < audioBufferIndexMin ) { | |
| audioBufferIndex = audioBufferIndexMax; | |
| } | |
| /* | |
| if ( i < audioBufferIndexMax / 2 ) { | |
| //if ( i < Math.random() * audioBufferIndexMax ) { | |
| audioBufferIndex++; | |
| } else { | |
| audioBufferIndex--; | |
| } | |
| */ | |
| /* | |
| audioBufferIndex++; | |
| //if ( audioBufferIndex > Math.random() * audioBufferIndexMax ) { | |
| if ( audioBufferIndex > audioBufferIndexMax ) { | |
| audioBufferIndex = audioBufferIndexMin; | |
| } | |
| */ | |
| //audioBufferIndex = Math.floor( Math.random() * audioBufferIndexMax ) + audioBufferIndexMin; | |
| } | |
| particles.push( particlesRow ); | |
| } | |
| }; | |
| //--- | |
| function onResize(){ | |
| w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; | |
| h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; | |
| canvas.width = w; | |
| canvas.height = h; | |
| context.fillStyle = '#000000'; | |
| context.fillRect( 0, 0, w, h ); | |
| imageData = context.getImageData( 0, 0, w, h ); | |
| data = imageData.data; | |
| }; | |
| //--- | |
| function audioBtHandler( event ) { | |
| if ( audio.paused ) { | |
| audio.play(); | |
| btStart.innerHTML = 'Pause Audio'; | |
| } else { | |
| audio.pause(); | |
| btStart.innerHTML = 'Play Audio'; | |
| } | |
| }; | |
| //--- | |
| function mouseDownHandler( event ) { | |
| mouseDown = true; | |
| }; | |
| function mouseUpHandler( event ) { | |
| mouseDown = false; | |
| }; | |
| function mouseEnterHandler( event ) { | |
| mouseActive = true; | |
| }; | |
| function mouseLeaveHandler( event ) { | |
| mouseActive = false; | |
| mousePos.x = w / 2; | |
| mousePos.y = h / 2; | |
| mouseDown = false; | |
| }; | |
| function mouseMoveHandler( event ) { | |
| mousePos = getMousePos( canvas, event ); | |
| }; | |
| function getMousePos( canvas, event ) { | |
| var rect = canvas.getBoundingClientRect(); | |
| return { x:event.clientX - rect.left, y:event.clientY - rect.top }; | |
| }; | |
| //--- | |
| function render() { | |
| var frequencySource; | |
| if ( analyser ) { | |
| frequencySource = new Uint8Array( analyser.frequencyBinCount ); | |
| analyser.getByteFrequencyData( frequencySource ); | |
| } | |
| //--- | |
| var sortArray = false; | |
| //--- | |
| for ( var i = 0, l = particles.length; i < l; i++ ) { | |
| var particlesRow = particles[ i ]; | |
| var particlesRowBack; | |
| if ( i > 0 ) { | |
| particlesRowBack = particles[ i - 1 ]; | |
| } | |
| //--- | |
| var center = particlesCenter[ i ]; | |
| if ( mouseActive ) { | |
| //center.x = ( ( w / 2 ) - mousePos.x ) * ( -i / 150 ) + w / 2; | |
| //center.y = ( ( h / 2 ) - mousePos.y ) * ( -i / 150 ) + h / 2; | |
| center.x = ( ( w / 2 ) - mousePos.x ) * ( ( particlesRow[ 0 ].z - fov ) / 500 ) + w / 2; | |
| center.y = ( ( h / 2 ) - mousePos.y ) * ( ( particlesRow[ 0 ].z - fov ) / 500 ) + h / 2; | |
| } else { | |
| center.x += ( ( w / 2 ) - center.x ) * mouseFollowSpeed; | |
| center.y += ( ( h / 2 ) - center.y ) * mouseFollowSpeed; | |
| } | |
| //--- | |
| for ( var j = 0, k = particlesRow.length; j < k; j++ ) { | |
| var particle = particlesRow[ j ]; | |
| var scale = fov / ( fov + particle.z ); | |
| particle.x2d = ( particle.x * scale ) + center.x; | |
| particle.y2d = ( particle.y * scale ) + center.y; | |
| //--- | |
| if ( analyser ) { | |
| var frequency = frequencySource[ particle.audioBufferIndex ]; | |
| var frequencyAdd = frequency / 8; | |
| particle.radiusAudio = particle.radius + frequencyAdd; | |
| } else { | |
| particle.radiusAudio = particle.radius;// + Math.random() * 4; | |
| } | |
| //--- | |
| if ( mouseDown ) { | |
| particle.z += speed; | |
| if ( particle.z > fov ) { | |
| particle.z -= ( fov * 2 ); | |
| sortArray = true; | |
| } | |
| } else { | |
| particle.z -= speed; | |
| if ( particle.z < -fov ) { | |
| particle.z += ( fov * 2 ); | |
| sortArray = true; | |
| } | |
| } | |
| //--- | |
| var lineColorValue = 0; | |
| if ( j > 0 ) { | |
| var p = particlesRow[ j - 1 ]; | |
| //var lineColorValue = Math.round( ( ( i - ( fov / 5 ) ) / l ) * 255 ); | |
| lineColorValue = Math.round( i / l * 200 );//255 | |
| /* | |
| if ( analyser ) { | |
| lineColorValue = Math.round( i / l * ( 200 + frequency ));//255 | |
| if ( lineColorValue > 255 ) { | |
| lineColorValue = 255; | |
| } | |
| } | |
| */ | |
| drawLine( particle.x2d | 0, particle.y2d | 0, p.x2d | 0, p.y2d | 0, 0, Math.round( lineColorValue / 2 ), lineColorValue, 255 ); | |
| } | |
| var position; | |
| if ( j < k - 1 ) { | |
| position = getCirclePosition( particle.centerX, particle.centerY, particle.radiusAudio, particle.index, particle.segments ); | |
| } else { | |
| var p1 = particlesRow[ 0 ]; | |
| position = getCirclePosition( p1.centerX, p1.centerY, p1.radiusAudio, p1.index, p1.segments ); | |
| } | |
| particle.x = position.x; | |
| particle.y = position.y; | |
| //--- | |
| if ( i > 0 && i < l - 1 ) { | |
| var pB;// = particlesRowBack[ j ]; | |
| if ( j === 0 ) { | |
| pB = particlesRowBack[ particlesRowBack.length - 1 ]; | |
| } else { | |
| pB = particlesRowBack[ j - 1 ]; | |
| } | |
| drawLine( particle.x2d | 0, particle.y2d | 0, pB.x2d | 0, pB.y2d | 0, 0, Math.round( lineColorValue / 2 ), lineColorValue, 255 ); | |
| } | |
| } | |
| } | |
| //--- | |
| if ( sortArray ) { | |
| particles = particles.sort( function( a, b ) { | |
| return ( b[ 0 ].z - a[ 0 ].z ); | |
| } ); | |
| } | |
| //--- | |
| if ( mouseDown ) { | |
| time -= 0.005; | |
| } else { | |
| time += 0.005; | |
| } | |
| //--- | |
| //soft invert colors | |
| if ( mouseDown ) { | |
| if ( colorInvertValue < 255 ) | |
| colorInvertValue += 5; | |
| else | |
| colorInvertValue = 255; | |
| softInvert( colorInvertValue ); | |
| } else { | |
| if ( colorInvertValue > 0 ) | |
| colorInvertValue -= 5; | |
| else | |
| colorInvertValue = 0; | |
| if ( colorInvertValue > 0 ) | |
| softInvert( colorInvertValue ); | |
| } | |
| }; | |
| //--- | |
| function softInvert( value ) { | |
| for ( var j = 0, n = data.length; j < n; j += 4 ) { | |
| data[ j ] = Math.abs( value - data[ j ] ); // red | |
| data[ j + 1 ] = Math.abs( value - data[ j + 1 ] ); // green | |
| data[ j + 2 ] = Math.abs( value - data[ j + 2 ] ); // blue | |
| data[ j + 3 ] = 255;// - data[ j + 3 ]; // alpha | |
| } | |
| }; | |
| //--- | |
| function animate() { | |
| clearImageData(); | |
| render(); | |
| context.putImageData( imageData, 0, 0 ); | |
| requestAnimationFrame( animate ); | |
| }; | |
| window.requestAnimFrame = ( function() { | |
| return window.requestAnimationFrame || | |
| window.webkitRequestAnimationFrame || | |
| window.mozRequestAnimationFrame || | |
| function( callback ) { | |
| window.setTimeout( callback, 1000 / 60 ); | |
| }; | |
| } )(); | |
| //--- | |
| init(); |
| html, body, div { | |
| margin: 0; | |
| padding: 0; | |
| border: 0; | |
| } | |
| body { | |
| overflow: hidden; | |
| background-color:#000; | |
| } | |
| .bt { | |
| -moz-box-shadow:inset 0px 1px 0px 0px #54a3f7; | |
| -webkit-box-shadow:inset 0px 1px 0px 0px #54a3f7; | |
| box-shadow:inset 0px 1px 0px 0px #54a3f7; | |
| background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #007dc1), color-stop(1, #0061a7)); | |
| background:-moz-linear-gradient(top, #007dc1 5%, #0061a7 100%); | |
| background:-webkit-linear-gradient(top, #007dc1 5%, #0061a7 100%); | |
| background:-o-linear-gradient(top, #007dc1 5%, #0061a7 100%); | |
| background:-ms-linear-gradient(top, #007dc1 5%, #0061a7 100%); | |
| background:linear-gradient(to bottom, #007dc1 5%, #0061a7 100%); | |
| background-color:#007dc1; | |
| -moz-border-radius:3px; | |
| -webkit-border-radius:3px; | |
| border-radius:3px; | |
| border:1px solid #124d77; | |
| display:inline-block; | |
| cursor:pointer; | |
| color:#ffffff; | |
| font-family:Arial; | |
| font-size:13px; | |
| padding:6px 24px; | |
| text-decoration:none; | |
| text-shadow:0px 1px 0px #154682; | |
| } | |
| .bt:hover { | |
| background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #0061a7), color-stop(1, #007dc1)); | |
| background:-moz-linear-gradient(top, #0061a7 5%, #007dc1 100%); | |
| background:-webkit-linear-gradient(top, #0061a7 5%, #007dc1 100%); | |
| background:-o-linear-gradient(top, #0061a7 5%, #007dc1 100%); | |
| background:-ms-linear-gradient(top, #0061a7 5%, #007dc1 100%); | |
| background:linear-gradient(to bottom, #0061a7 5%, #007dc1 100%); | |
| background-color:#0061a7; | |
| } | |
| .bt:active { | |
| position:relative; | |
| top:1px; | |
| } | |
| #btStartAudioVisualization { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| } | |
| #txtStatus { | |
| position: absolute; | |
| bottom: 0px; | |
| left: 10px; | |
| font-family: "Arial", serif; | |
| font-size: 12px; | |
| color: rgb(255,255,255); | |
| } |