Need assistance with audio visualizer using HTML5 canvas + web audio API - javascript

I'm looking at making a simple audio visualizer using canvas and the web audio API. I found this example online and want to learn more in depth to what exactly each part of the code is doing. I have some understanding to a few things.
Also importantly I also wish to change the canvas background color but have yet been successful in trying to do so.
var analyser, canvas, ctx, random = Math.random, circles = [];
window.onload = function() {
canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
setupWebAudio();
for (var i = 0; i < 280; i++) {
circles[i] = new Circle();
circles[i].draw();
}
draw();
};
function setupWebAudio() {
var audio = document.createElement('audio');
audio.src = 'flume.mp3';
document.body.appendChild(audio);
var audioContext = new AudioContext();
analyser = audioContext.createAnalyser();
var source = audioContext.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioContext.destination);
audio.play();
}
function draw() {
requestAnimationFrame(draw);
var freqByteData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(freqByteData);
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 1; i < circles.length; i++) {
circles[i].radius = freqByteData[i] * 1;
circles[i].y = circles[i].y > canvas.height ? 0 : circles[i].y + 1;
circles[i].draw();
}
}
function getRandomColor(){
return random() * 1 >> 0;
}
function Circle() {
this.x = random() * canvas.width;
this.y = random() * canvas.height;
this.radius = random() * 20 + 20;
this.color = 'rgb(' + getRandomColor() + ',' + getRandomColor() + ',' +
getRandomColor() + ')';
}
Circle.prototype.draw = function() {
var that = this;
ctx.save();
ctx.beginPath();
ctx.globalAlpha = random() / 8 + 0.2;
ctx.arc(that.x, that.y, that.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}

Change the color in draw() function.
This lines creates a rectangle as the background.
ctx.clearRect(0, 0, canvas.width, canvas.height);
Just before it, set the color, e.g.:
ctx.fillStyle = rgb(1, 2, 3);
ctx.clearRect(0, 0, canvas.width, canvas.height);

Related

Clear canvas after ctx.arc was clicked

So the objective is to place a circle randomly on the screen, and if the circle was clicked, remove this old circle and make a new circle.
I'm having a problem with removing the old point and making a new one. Instead of removing the old one, it keeps it, makes a new circle and eventually does whatever this is after a bit of clicking:
This is how my code looks like:
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.cursor = "crosshair";
function randSpot() {
var X = Math.floor(Math.random() * canvas.width) + 10;
var Y = Math.floor(Math.random() * canvas.height) + 10;
ctx.arc(X, Y, 5.5, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
document.body.addEventListener('click', function(e) {
if (ctx.isPointInPath(e.clientX, e.clientY)) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
randSpot()
}
});
}
randSpot()
What am I doing wrong and how can I fix it?
You forgot to begin a new path
ctx.beginPath();
I had to modify the code to make it run in the snippet
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.cursor = "crosshair";
function randSpot() {
var X = Math.floor(Math.random() * canvas.width) + 10;
var Y = Math.floor(Math.random() * canvas.height) + 10;
ctx.beginPath();
ctx.arc(X, Y, 5.5, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
}
randSpot()
document.body.addEventListener('click', function(e) {
// I had to remove the following condition
// ctx.isPointInPath(e.clientX, e.clientY)
// because the code didn't wanted to work with the snippet
// but it's unrelated to the problem
ctx.clearRect(0, 0, canvas.width, canvas.height);
randSpot()
});

removing the js script from html page

<!DOCTYPE html5>
<html>
<head>
<title>disturbed</title>
<script>
window.onload = function() {
// create the canvas
var canvas = document.createElement("canvas"),
c = canvas.getContext("2d");
var particles = {};
var particleIndex = 0;
var particleNum = 15;
// set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// add canvas to body
document.body.appendChild(canvas);
// style the canvas
c.fillStyle = "black";
c.fillRect(0, 0, canvas.width, canvas.height);
function Particle() {
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.gravity = 0.3;
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = Math.random() * 30 + 60;
this.color = "hsla(" + parseInt(Math.random() * 360, 10) + ",90%,60%,0.5";
}
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
if (Math.random() < 0.1) {
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
}
this.life++;
if (this.life >= this.maxLife) {
delete particles[this.id];
}
c.fillStyle = this.color;
//c.fillRect(this.x, this.y, 5, 10);
c.beginPath();
c.arc(this.x, this.y, 2.5, 0, 2 * Math.PI);
c.fill();
};
setInterval(function() {
//normal setting before drawing over canvas w/ black background
c.globalCompositeOperation = "source-over";
c.fillStyle = "rgba(0,0,0,0.5)";
c.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particleNum; i++) {
new Particle();
}
// c.globalCompositeOperation = "darken";
for (var i in particles) {
particles[i].draw();
}
}, 30);
};
</script>
</head>
<body>
</body>
</html>
The code below is all correct but its just two densed i want to make it easier and not to populated . what i want to do is make the script that i have into a separate file like "anything.js" so that i can load it into my html main file by just calling out the main functions like particle() in ,window.onload = function() which will be on the main page .
The reason is because i want to add this script to many html pages and i dont want to copy all of the lengthy script in to my code again and again .
Please answer this , it would be really helful.
HTML :
<!DOCTYPE html5>
<html>
<head>
<title>disturbed</title>
</head>
<body>
<script src="toto.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function() {
Particle();
};
</script>
</body>
</html>
toto.js :
//create the canvas
var canvas = document.createElement("canvas"),
c = canvas.getContext("2d");
var particles = {};
var particleIndex = 0;
var particleNum = 15;
// set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// add canvas to body
document.body.appendChild(canvas);
// style the canvas
c.fillStyle = "black";
c.fillRect(0, 0, canvas.width, canvas.height);
function Particle() {
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.gravity = 0.3;
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = Math.random() * 30 + 60;
this.color = "hsla(" + parseInt(Math.random() * 360, 10) + ",90%,60%,0.5";
}
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
if (Math.random() < 0.1) {
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
}
this.life++;
if (this.life >= this.maxLife) {
delete particles[this.id];
}
c.fillStyle = this.color;
//c.fillRect(this.x, this.y, 5, 10);
c.beginPath();
c.arc(this.x, this.y, 2.5, 0, 2 * Math.PI);
c.fill();
};
setInterval(function() {
//normal setting before drawing over canvas w/ black background
c.globalCompositeOperation = "source-over";
c.fillStyle = "rgba(0,0,0,0.5)";
c.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particleNum; i++) {
new Particle();
}
// c.globalCompositeOperation = "darken";
for (var i in particles) {
particles[i].draw();
}
}, 30);
I see you have a scope issue.
Variables are passed from script to script. However, in your case, you declare Particle inside window.onload so it only gets defined inside it and you can't use it elsewhere.
The right way to export your script into a separate file would be to declare Particle in the scope of the whole script, as in:
// anything.js
function Particle() {
...
}
Note that you'd need a bit of rewriting, since I can see that you use variables like canvas (which are only defined in the scope of window.onload) inside Particle's code.

JS target function comes to constructor not working

I am working on drawing moving rectangles on my canvas. I made a template function for the test purpose and it works, but since i want to draw more of the rectangles with same animation effect I have to make this template function comes to the constructor, getContextand now the problem occurs:
the template function:
ctx = getContext('2d');
var boxHeight = canvas.height/40;
var boxWidth = canvas.width/20;
function drawBox(){
var x = 20;
var y = canvas.height;
var w = boxWidth;
var h = boxHeight;
var timer = 0;
var ladder = Math.floor(Math.random()*((canvas.height*0.5)/boxHeight)) + 1;
for(var i = 0; i < ladder; i++){
ctx.fillStyle = 'hsl('+Math.abs(Math.sin(timer) * 255)+', 40%, 50%)';
ctx.fillRect(x,y,w,h);
ctx.strokeRect(x,y,w,h);
ctx.lineWidth = 2;
ctx.stroke();
y -= boxHeight;
timer += Math.random()*0.3;
}
}
function animate(){
ctx.clearRect(0,0,canvas.width,canvas.height);
window.requestAnimationFrame(animate);
drawBox();
}
animate();
this template function drawBox()just working fine, then i tried to enclose its properties into a Box()constructor object:
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
this.colorTimer = 0;
this.draw = function() {
this.colorTimer += Math.random() * 0.3;
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / boxHeight)) + 1;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(this.colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
this.postion.y -= this.height;
}
}
}
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
myBox.draw();
}
animate();
this is not working, i have been stuck with this about 2 hours and i don't think there is any method or properties difference between my Boxconstructor and my drawBoxobject. When it comes to myBoxobject to calling its draw()method, there is nothing pop out on the screen.
I am wondering did i just miss something important when creating Boxconstructor object? Could someone give me a hint please?
As #Todesengel mentioned, the real issue here is, you are re-initializing all the variables each time the template function (drawBox) is called. But you are not doing the same for the constructor. To resolve this, put this.colorTimer = 0 and this.postion.y = canvas.height insde the draw method (as these are the variables that need to be re-initialized).
However, there are other issues :
you are increasing the timer variable inside the for loop, in template function, but not doing the same for constructor
as #Barmar mentioned, you should define draw method as Box.prototype.draw, for efficiency (not mandatory though)
Here is the revised version of your code :
ctx = canvas.getContext('2d');
var boxHeight = canvas.height / 40;
var boxWidth = canvas.width / 20;
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
}
Box.prototype.draw = function() {
this.colorTimer = 0;
this.postion.y = canvas.height;
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / this.height)) + 1;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(this.colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
this.postion.y -= this.height;
this.colorTimer += Math.random() * 0.3;
}
}
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
myBox.draw();
window.requestAnimationFrame(animate);
}
animate();
<canvas id="canvas" width="300" height="300"></canvas>
I believe the important thing to note here is that in your first case, there is a new drawBox function being called every time, with the variables being instantiated and initialized, or "reset", each time. In your second case, the myBox object is not being recreated each time, so you have left over variables. These will not behave the same way. It should work as expected if you move var myBox = new Box(20, boxWidth); into the animate function.
Another fix, if you don't want to do recreate the myBox object for each call, is to reset the left over variables after each animate call. It would be more efficient, and probably more desirable, to do it this way.
You should not modify this.position.y in the draw method.
So remove this assignment from the loop:
this.postion.y -= this.height;
... and change the following lines to dynamically add -i*this.height:
ctx.fillRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
As others have said, you should better define the method on the prototype. And colorTimer should change in the for loop. I think you could do with a local variable though.
Demo:
var ctx = canvas.getContext('2d');
var boxHeight = canvas.height/40;
var boxWidth = canvas.width/20;
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
}
Box.prototype.draw = function() {
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / boxHeight)) + 1;
var colorTimer = 0;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
colorTimer += Math.random() * 0.3;
}
};
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
myBox.draw();
}
animate();
<canvas id="canvas"></canvas>

Constructor, loops and arrays practice in vanilla JS

I'm trying to make an animation where particles flow from one side the other like a flock of birds. You can see a live version on my semi-finished portfolio here: https://benjamingibbsportfolio.000webhostapp.com/
I'm in the process of learning constructor functions, so I've decided to redo the above project using that type of programming.
I've managed to basically complete it, apart from the fact it only displays one particle/flake - it should show 100?
for(var i = 0; i < 100; i++)
{
flakes[i] = new Flake();
}
Where have I gone wrong?
Are all the particles simply being drawn at the same place?
I've uploaded the code here: https://jsfiddle.net/q7ja8qxv/
A single flake is drawn with this function:
this.display = function() {
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#ffff00";
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
ctx.fill();
}
The source of the problem is the first line of the function:
ctx.clearRect(0, 0, w, h);
This clears the whole canvas...
This means that every flake "erases" the previous flake(s).
As a fix you have to remove the ctx.clearRect() call from Flake.display() and instead call it in a place where it is only executed once, before you start drawing the flakes. For example in drawFlakes() right before the loop:
function drawFlakes() {
ctx.clearRect(0, 0, w, h);
for (i = 0; i < flakes.length; i++) {
flakes[i].display();
flakes[i].move();
flakes[i].update();
}
angle += 0.01;
}
Complete example:
var canvas = document.getElementById('stars');
var ctx = canvas.getContext('2d')
var w = window.innerWidth;
var h = window.innerHeight;
var flakes = [];
var angle = 0;
canvas.width = w;
canvas.height = h;
for (var i = 0; i < 20; i++) {
flakes[i] = new Flake();
}
function drawFlakes() {
ctx.clearRect(0, 0, w, h);
for (i = 0; i < flakes.length; i++) {
flakes[i].display();
flakes[i].move();
flakes[i].update();
}
angle += 0.01;
}
function Flake() {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.r = Math.random() * 5 + 2;
this.d = Math.random() * 1;
this.display = function() {
ctx.fillStyle = "#ffff00";
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
ctx.fill();
}
this.move = function() {
this.y += Math.pow(this.d, 2) + 1;
this.x += Math.sin(angle) * 60;
};
this.update = function() {
if (this.y > h) {
this.x = Math.random() * w;
this.y = 0;
this.r = this.r;
this.d = this.d;
}
};
}
setInterval(drawFlakes, 25);
body {
background: black;
overflow: hidden;
}
<canvas id="stars"></canvas>

Picture background for canvas particle

I'm trying to create background for every created particle.
Canvas pattern is not working properly. I'm getting this error >> "SyntaxError: An invalid or illegal string was specified"
HTML
<canvas id="canvas"></canvas>
JS
(function(){
window.onload = function(){
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d'),
particles = {},
particleIndex = 0,
particleNum = 1;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.width);
function Particle(){
this.x = canvas.width / 2;
this.y = 0;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.gravity = 0.2;
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.test = 0;
this.maxLife = 100;
}
Particle.prototype.draw = function(){
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity;
this.test++;
if ( this.test >= this.maxLife ) {
delete particles[this.id];
};
var img = new Image();
img.src = 'img/aaa.png';
var pattern = ctx.createPattern(img,'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(this.x, this.y, 20, 20);
};
setInterval(function(){
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particleNum; i++) {
new Particle();
};
for(var i in particles) {
particles[i].draw();
}
},30)
}})();
I was trying to do this also with ctx.drawImage(), but picture was displayed one time only.
Any tips?:)
Fiddle
I think the issue is the image being loaded later than its used.
Inside your draw() you have:
var img = new Image();
img.src = 'img/aaa.png';
var pattern = ctx.createPattern(img,20,20);
It is a bad idea to create new images everytime you want to draw. I strongly suggest you create the img variable outside of the draw loop. Once you set the .src of an image, you have to wait until it is loaded to use it. There is an onload event you can use to let you know when its ready.
Here is an example:
var imgLoaded = false;
var img = new Image();
img.src = 'img/aaa.png';
img.onload = function() { imgLoaded = true; };
function draw() {
...
if (imgLoaded) {
var pattern = ctx.createPattern(img, 'repeat');
...
}
...
}

Categories

Resources