Threejs Audio Visualizer
A Pen by Ion Drimba Filho on CodePen.
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<meta name="theme-color" content="#f15f48"> | |
<meta name="application-name" content="React Calculator"> | |
<meta name="description" content="Threejs Audio Visualizer"> | |
<meta name="keywords" content="Threejs, Javascript, Ion Drimba Filho"> | |
<meta name="subject" content="Threejs Audio Visualizer"> | |
<meta name="copyright" content="Ion Drimba Filho"> | |
<meta name="robots" content="index,follow"> | |
<meta name="topic" content=""> | |
<meta name="summary" content="ThreeJs Audio Visualizer Two"> | |
<meta name="author" content="Ion Drimba Filho"> | |
<meta name="url" content="http://iondrimba.github.io/threejs-audio-visualizer/public/index.html"> | |
<meta name="pagename" content="ThreeJs Audio Visualizer Two"> | |
<meta name="category" content=""> | |
<meta name="coverage" content="Worldwide"> | |
<meta name="distribution" content="Global"> | |
<meta name="rating" content="General"> | |
<meta name="subtitle" content="ThreeJs Audio Visualizer Two"> | |
<meta name="target" content="all"> | |
<meta http-equiv="cleartype" content="on"> | |
<meta name="twitter:card" content="summary_large_image"> | |
<meta name="twitter:site" content="Threejs Audio Visualizer Two"> | |
<meta name="twitter:creator" content="Ion Drimba Filho"> | |
<meta name="twitter:title" content="Threejs Audio Visualizer Two"> | |
<meta name="twitter:description" content="ThreeJs Audio Visualizer Two"> | |
<meta name="twitter:image:src" content="https://raw.githubusercontent.com/iondrimba/images/master/demo2.PNG"> | |
<meta property="og:url" content="http://iondrimba.github.io/threejs-audio-visualizer/public/index.html"> | |
<meta property="og:type" content="website"> | |
<meta property="og:title" content="Threejs Audio Visualizer"> | |
<meta property="og:image" content="https://raw.githubusercontent.com/iondrimba/images/master/demo2.PNG"> | |
<meta property="og:description" content="Threejs Audio Visualizer Two"> | |
<meta property="og:site_name" content="Threejs Audio Visualizer Two"> | |
<meta property="article:author" content="https://iondrimbafilho.me/"> | |
<meta property="article:publisher" content="https://iondrimbafilho.me/"> | |
<meta itemprop="name" content="ThreeJs Audio Visualizer Two"> | |
<meta itemprop="description" content="Threejs Audio Visualizer"> | |
<meta itemprop="image" content="https://raw.githubusercontent.com/iondrimba/images/master/demo2.PNG"> | |
<meta name="apple-mobile-web-app-capable" content="yes"> | |
<meta name="mobile-web-app-capable" content="yes"> | |
<title>Threejs Audio Visualizer Two</title> | |
<link href="https://fonts.googleapis.com/css?family=Ropa+Sans" rel="stylesheet"> | |
</head> | |
<body> | |
<button class="play-intro"> | |
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 32 32" | |
data-tags="play,media control"> | |
<g fill="#fff" transform="scale(0.03125 0.03125)"> | |
<path d="M192 0v1024l640-511.264-640-512.736z" /> | |
</g> | |
</svg> | |
</button> | |
<div class="credits"> | |
<h1> | |
<a href="https://comtruise.bandcamp.com/album/in-decay" target="_blank" rel="noopener noreferrer">Com Truise </a>/ | |
<a href="https://comtruise.bandcamp.com/track/84-dreamin" target="_blank" rel="noopener noreferrer">84' Dreamin</a> | |
</h1> | |
<div class="controls"> | |
<button class="play"> | |
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="32" height="32" viewBox="0 0 25 32" | |
data-tags="play,media control"> | |
<g fill="#fff" transform="scale(0.03125 0.03125)"> | |
<path d="M192 0v1024l640-511.264-640-512.736z" /> | |
</g> | |
</svg> | |
</button> | |
<button class="pause"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32" | |
data-tags="pause,media control"> | |
<g fill="#fff" transform="scale(0.03125 0.03125)"> | |
<path d="M352 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32zM864 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32z" | |
/> | |
</g> | |
</svg> | |
</button> | |
</div> | |
</div> | |
<div class="loader"></div> | |
<audio id="audio" crossOrigin="anonymous"></audio> | |
</body> | |
</html> |
class Loader { | |
constructor() { | |
this.callback = null; | |
} | |
load(file) { | |
const request = new XMLHttpRequest(); | |
request.open('GET', file, true); | |
request.onprogress = (evt) => { | |
let percent = Math.round((evt.loaded / evt.total) * 100); | |
this.callback(percent); | |
}; | |
request.onload = () => { this.complete(file); }; | |
request.send(); | |
} | |
progress(callback) { this.callback = callback; }; | |
complete() { } | |
} | |
class App { | |
constructor() { | |
this.loader = new Loader(); | |
this.loader.progress((percent) => { | |
this.progress(percent); | |
}); | |
this.playIntro = document.querySelector('.play-intro'); | |
this.loaderBar = document.querySelector('.loader'); | |
this.loader.load('https://iondrimbafilho.me/demo.mp3'); | |
this.loader.complete = this.complete.bind(this); | |
this.count = 0; | |
this.percent = 0; | |
this.playing = false; | |
this.objects = []; | |
} | |
progress(percent) { | |
this.loaderBar.style.transform = `scale(${percent / 100}, 1)`; | |
if (percent === 100) { | |
setTimeout(() => { | |
requestAnimationFrame(() => { | |
this.playIntro.classList.add('control-show'); | |
this.loaderBar.classList.add('removeLoader'); | |
this.loaderBar.style.transform = 'scale(1, 0)'; | |
}) | |
}, 300); | |
} | |
} | |
complete(file) { | |
setTimeout(() => { | |
this.firstRing = new THREE.Object3D(); | |
this.secondRing = new THREE.Object3D(); | |
this.thirdRing = new THREE.Object3D(); | |
this.fourthRing = new THREE.Object3D(); | |
this.setupAudio(); | |
this.addSoundControls(); | |
this.createScene(); | |
this.createCamera(); | |
this.addAmbientLight(); | |
this.addSpotLight(); | |
this.addCameraControls(); | |
this.addFloor(); | |
this.createRingOfSquares(20, 1, 0x4250ca, this.firstRing); | |
this.createRingOfSquares(30, 2, 0x721fa1, this.secondRing); | |
this.createRingOfSquares(40, 3, 0xf95c38, this.thirdRing); | |
this.createRingOfSquares(50, 4, 0x5e0f86, this.fourthRing); | |
this.animate(); | |
this.playSound(file); | |
}, 200); | |
document.addEventListener('visibilitychange', (evt) => { | |
if(evt.target.hidden) { | |
this.pause(); | |
} else { | |
this.play(); | |
} | |
}, false); | |
} | |
addSoundControls() { | |
this.btnPlay = document.querySelector('.play'); | |
this.btnPause = document.querySelector('.pause'); | |
this.btnPlay.addEventListener('click', () => { | |
this.play(); | |
}); | |
this.btnPause.addEventListener('click', () => { | |
this.pause(); | |
}); | |
} | |
createRingOfSquares(count, radius, color, group) { | |
const size = .5; | |
const geometry = new THREE.BoxGeometry(size, size, size); | |
const material = new THREE.MeshLambertMaterial({ | |
color | |
}); | |
for (let index = 0; index < count; index++) { | |
const l = 360 / count; | |
const pos = this.radians(l * index); | |
const obj = this.createObj(color, geometry, material); | |
const distance = (radius * 2); | |
const sin = Math.sin(pos) * distance; | |
const cos = Math.cos(pos) * distance; | |
obj.position.set(sin, 0, cos); | |
obj.rotateY(pos); | |
this.objects.push(obj); | |
group.add(obj); | |
} | |
this.scene.add(group); | |
} | |
play() { | |
this.audioCtx.resume(); | |
this.audioElement.play(); | |
this.btnPlay.classList.remove('control-show'); | |
this.btnPause.classList.add('control-show'); | |
} | |
pause() { | |
this.audioElement.pause(); | |
this.btnPause.classList.remove('control-show'); | |
this.btnPlay.classList.add('control-show'); | |
} | |
createScene() { | |
this.scene = new THREE.Scene(); | |
this.scene.background = new THREE.Color(0xf15f48); | |
this.renderer = new THREE.WebGLRenderer({ antialias: true }); | |
this.renderer.setSize(window.innerWidth, window.innerHeight); | |
this.renderer.shadowMap.enabled = true; | |
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
document.body.appendChild(this.renderer.domElement); | |
} | |
createCamera() { | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
this.camera = new THREE.PerspectiveCamera(70, width/ height, 1, 1000); | |
this.camera.position.set(-12, 17, 10); | |
this.scene.add(this.camera); | |
} | |
addCameraControls() { | |
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); | |
this.controls.enableDamping = true; | |
this.controls.dampingFactor = 0.04; | |
document.body.style.cursor = "-moz-grabg"; | |
document.body.style.cursor = "-webkit-grab"; | |
this.controls.addEventListener("start", () => { | |
requestAnimationFrame(() => { | |
document.body.style.cursor = "-moz-grabbing"; | |
document.body.style.cursor = "-webkit-grabbing"; | |
}); | |
}); | |
this.controls.addEventListener("end", () => { | |
requestAnimationFrame(() => { | |
document.body.style.cursor = "-moz-grab"; | |
document.body.style.cursor = "-webkit-grab"; | |
}); | |
}); | |
} | |
createObj(color, geometry, material) { | |
const obj = new THREE.Mesh(geometry, material); | |
obj.castShadow = true; | |
obj.receiveShadow = true; | |
return obj; | |
} | |
onResize() { | |
const ww = window.innerWidth; | |
const wh = window.innerHeight; | |
this.camera.aspect = ww / wh; | |
this.camera.updateProjectionMatrix(); | |
this.renderer.setSize(ww, wh); | |
} | |
addFloor() { | |
const planeGeometry = new THREE.PlaneGeometry(2000, 2000); | |
const planeMaterial = new THREE.ShadowMaterial({ opacity: 0.08 }); | |
const plane = new THREE.Mesh(planeGeometry, planeMaterial); | |
planeGeometry.rotateX(- Math.PI / 2); | |
plane.position.y = -1; | |
plane.receiveShadow = true; | |
this.scene.add(plane); | |
} | |
moveRingGroup(group, value) { | |
group.rotation.y += value; | |
} | |
addSpotLight() { | |
const spotLight = new THREE.SpotLight(0xffffff); | |
spotLight.position.set(0, 20, 1); | |
spotLight.castShadow = true; | |
this.scene.add(spotLight); | |
const spotLightHelper = new THREE.SpotLightHelper(spotLight); | |
} | |
addAmbientLight() { | |
const light = new THREE.AmbientLight(0xffffff); | |
this.scene.add(light); | |
} | |
animate() { | |
this.controls.update(); | |
this.drawWave(); | |
this.renderer.render(this.scene, this.camera); | |
requestAnimationFrame(this.animate.bind(this)); | |
} | |
radians(degrees) { | |
return degrees * Math.PI / 180; | |
} | |
drawWave() { | |
if (this.playing) { | |
this.analyser.getByteFrequencyData(this.frequencyData); | |
for (var i = 0; i < 140; i++) { | |
const p = this.frequencyData[i]; | |
const s = this.objects[i]; | |
const z = s.position; | |
TweenMax.to(z, .2, { | |
y: p / 20 | |
}); | |
} | |
} | |
this.moveRingGroup(this.firstRing, .01); | |
this.moveRingGroup(this.secondRing, -.01); | |
this.moveRingGroup(this.thirdRing, .02); | |
this.moveRingGroup(this.fourthRing, -.02); | |
} | |
setupAudio() { | |
this.audioElement = document.getElementById('audio'); | |
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
this.analyser = this.audioCtx.createAnalyser(); | |
this.source = this.audioCtx.createMediaElementSource(this.audioElement); | |
this.source.connect(this.analyser); | |
this.source.connect(this.audioCtx.destination); | |
this.bufferLength = this.analyser.frequencyBinCount; | |
this.frequencyData = new Uint8Array(this.bufferLength); | |
this.audioElement.volume = .5; | |
this.audioElement.addEventListener('playing', () => { | |
this.playing = true; | |
}); | |
this.audioElement.addEventListener('pause', () => { | |
this.playing = false; | |
}); | |
this.audioElement.addEventListener('ended', () => { | |
this.playing = false; | |
}); | |
} | |
playSound(file) { | |
setTimeout(() => { | |
this.playIntro.addEventListener('click', (evt)=>{ | |
evt.currentTarget.classList.remove('control-show'); | |
this.play(); | |
}); | |
this.audioElement.src = file; | |
}, 500); | |
} | |
} | |
window.app = new App(); | |
window.addEventListener('resize', app.onResize.bind(app)); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r123/three.min.js"></script> | |
<script src="https://Threejs.org/examples/js/controls/OrbitControls.js"></script> |
Threejs Audio Visualizer
A Pen by Ion Drimba Filho on CodePen.
html, body { | |
margin: 0; | |
padding: 0; | |
font-family: 'Ropa Sans', sans-serif; | |
background-color: #f15f48; | |
color: #fff; | |
box-sizing: border-box; | |
overflow: hidden; | |
cursor: -webkit-grab; | |
cursor: -moz-grab; | |
} | |
canvas { | |
width: 100%; | |
height: 100%; | |
} | |
h1 { | |
padding: 0; | |
margin: 0; | |
color: inherit; | |
} | |
.play-intro { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
display: none; | |
transform: translate(-50%, -50%); | |
height: 100px; | |
width: 100px; | |
cursor: pointer; | |
} | |
.play-intro svg { | |
width: 100%; | |
} | |
button { | |
background-color: transparent; | |
border: 0; | |
cursor: pointer; | |
} | |
.controls { | |
position: absolute; | |
right: 30px; | |
top: 10px; | |
} | |
.play { | |
display: none; | |
} | |
.pause { | |
display: none; | |
} | |
.control-show { | |
display: block; | |
} | |
.loader { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
top: 0; | |
z-index: 0; | |
transform: scale(0, 1); | |
transform-origin: left; | |
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); | |
background: linear-gradient(to right, #0391fd, #751ea1); | |
} | |
a { | |
color: inherit; | |
text-decoration: none; | |
} | |
.credits { | |
margin: 20px; | |
position: absolute; | |
z-index: 1; | |
bottom: 10px; | |
width: 100%; | |
} | |
.credits h1 { | |
font-size: 20pt; | |
width: 80%; | |
line-height: 1.4; | |
} | |
.credits .controls svg { | |
width: 50px; | |
} | |
.removeLoader { | |
transform-origin: bottom; | |
transition: transform 0.3s 0.3s cubic-bezier(0.23, 1, 0.32, 1); | |
} | |
@media screen and (min-width: 768px) { | |
.credits { | |
margin-left: 20px; | |
margin-top: 20px; | |
position: absolute; | |
z-index: 1; | |
bottom: inherit; | |
width: 100%; | |
} | |
.credits h1 { | |
font-size: 22pt; | |
width: auto; | |
line-height: 1.4; | |
} | |
.credits .controls { | |
right: 50px; | |
} | |
.credits .controls svg { | |
width: auto; | |
} | |
} |