Javascript canvas path not rendering - javascript

I'm new to canvas and I'm trying to create a small game. I'm trying to render some shapes on the screen but for some reason the shapes don't appear. The methods are being called and shapes get added to the array but nothing renders on the screen.
const canvas = document.getElementsByClassName('gameContainer');
const context = canvas[0].getContext('2d');
let canvasWidth = window.innerWidth;
let canvasHeight = window.innerHeight;
let shapes = [];
class Shape {
constructor(x, y, rad) {
this.x = x;
this.y = y;
this.rad = rad;
}
createShape() {
context.beginPath();
context.arc(this.x, this.y, this.rad, 0, 2 * Math.PI, false);
context.fillStyle = '#000';
context.fill();
}
}
class CanvasState {
constructor() {
this.canvas = canvas[0];
this.canvas.height = canvasHeight;
this.canvas.width = canvasWidth;
this.shapes = shapes;
}
addShape() {
setInterval(function() {
const randomRad = randomNum(10, 100);
const shape = new Shape(randomNum(randomRad, canvasWidth - randomRad), -randomRad, randomRad);
shape.createShape(this.context);
shapes.push(shape);
}, 1000);
}
}
function randomNum(min, max) {
Math.round(Math.random() * (max - min) + min);
}
function init() {
const cvs = new CanvasState();
cvs.addShape();
}
window.onload = function() {
init();
}
Any help given is greatly appreciated, thanks!

Assuming you have a working implementation of randomNum, the following lines result in the y-coordinate always being negative:
const randomRad = randomNum(10, 100);
const shape = new Shape(randomNum(randomRad, canvasWidth - randomRad), -randomRad, randomRad);
So all the shapes get drawn off screen.

Related

How to make moving canvas objects with images in HTML with JavaScript

So I'm working on a project where I have a canvas filled with moving balls. Its an extension/inspired by this codepen project : https://codepen.io/zetyler/pen/LergVR .
It essentially runs with the same physics in place as the codepen, but now I'm trying to draw the moving and colliding balls with images instead of random colors.
The original draw() method looks like this :
var pen = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
var numBalls = 30;
var grav = [0,-0.1];
function Ball(x,y,dx,dy,r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
this.color = 'hsl('+(Math.random()*360)+',90%,50%)';
this.draw = function() {
pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.fill();
}
I'm refactoring the draw method to try and work with an image instead of a random color fill, and so far I can't even get an image to show up. Currently my draw method looks like this:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
let numBalls = 1;
let grav = [0,-0.1];
//try feeding the ball function an object
//and destructuring the inputs
class Ball {
constructor (x, y, dx, dy, r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
//probably won't need this
//this.color = 'hsl(' + (Math.random() * 360) + ', 90%, 50%)';
}
draw() {
var thumbImg = document.createElement('img');
thumbImg.src = './svgs/javascriptIcon.svg';
thumbImg.onload = function() {
context.save();
context.beginPath();
context.arc(25, 25, 25, 0, Math.PI * 2, true);
context.closePath();
context.clip();
context.drawImage(thumbImg, 0, 0, 50, 50);
context.beginPath();
context.arc(0, 0, 25, 0, Math.PI * 2, true);
context.clip();
context.closePath();
context.restore();
};
}
It's been so long since I've used the html canvas. I can't figure out what I'm doing wrong. I thought I would at least be able to get the image to show up, but no such luck.
Thanks for checking it out! Please let me know what you think.
I am not sure why you are using ctx.clip,
but if you just want to replace the coloured balls with images try this in your draw method
this.draw = function() {
// make sure the img is loaded
//pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.drawImage(img,this.x, this.y,this.r, this.r)
pen.fill();
}
after that play with IMG x and y positions for example pen.drawImage(img,this.x + somefactor, this.y - somefactor,this.r + somefactor, this.r + somefatcor)
just to make sure that img is perfectly cover the coloured ball so it behavies just like it

All circles being drawn in the same colour [duplicate]

This question already has answers here:
Drawing lines with canvas by using for loop
(2 answers)
Closed 2 years ago.
Shortly summarized the problem is this. I want to draw two circles on the canvas with different colors. For some reason, they are drawn in the same color, even though the console log I have placed in is switching between "green" and "blue". Sorry that some of the variable names are in my native language if that poses a problem just ask and I'll translate it.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
obj.forEach(x => {
x.tegn();
});
}, 30);
You need to add a ctx.beginPath().
The reason you are seeing the same color is related to the same problem found in this question: Drawing lines with canvas by using for loop. If you don't use beginPath(), you keep pushing draw commands to the same (root) path and then drawing the ever increasingly complex path.
You have to use beginPath to start a sub-path. ctx.fill() will close the sub-path. The closePath is optional.
The third, and an optional step, is to call closePath(). This method
tries to close the shape by drawing a straight line from the current
point to the start. If the shape has already been closed or there's
only one point in the list, this function does nothing.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.beginPath();
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
ctx.clearRect(0,0,500,500);
obj.forEach(x => {
x.tegn();
});
}, 1000);
your paths are being drawn in the correct colors, but your second (blue) arc is drawing on top of your first (green) one. at the top of your tegn method, add a call to ctx.beginPath() to let your canvas know that your paths should be independent.
You missed beginPath.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.beginPath();
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
obj.forEach(x => {
x.tegn();
});
}, 30);

change shapre of img

I need help with Transform moving square image to circle in html canvas
I have imported a image that is a square. I want that image be round and still move.
Goal:
square image to circle image, and still able to move.
I have tried alot of tutorial on stackoverflow mainly with c. stroke and c.split but when I apply those the image doesnt move anymore.
Does someone have any suggestions?
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function Circle() {
//Give var for circle
this.x = 10;
this.y = 100;
this.dx = 1;
this.dy = 1;
this.radius = 50;
this.diameter = 2 * this.radius;
//Get external square picture (Needs to be converted in circle)
var image = new Image();
image.src = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
//Draw circle on canvas
this.draw = function () {
//Circle
c.beginPath();
c.arc(this.x, this.y, (this.radius*1), 0, Math.PI * 2, false);
c.closePath();
//TODO: cut square to circle
//Place square (image) on top of the circle
c.drawImage(image, (this.x-this.diameter/2) , (this.y-this.diameter/2), this.diameter, this.diameter);
};
//Update position
this.update = function () {
this.x += this.dx;
this.draw()
}
}
//Animate canvas
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
this.update();
}
//Start
Circle();
animate();
canvas {
border: 1px solid black;
background: black;
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas></canvas>
</body>
</html>
The answer is to use context.clip(); in your case c.clip(); this creates a clipping filter on the canvas that you can then draw in. Before you make a clip you must make a save and then restore after you draw with c.save(); and c.restore() respectively.
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
var circles = [];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function Circle() {
//Give var for circle
this.x = 100;
this.y = 100;
this.dx = 1;
this.dy = 1;
this.radius = 50;
this.diameter = 2 * this.radius;
this.size = null;
this.c = null;
//Get external square picture (Needs to be converted in circle)
var image = new Image();
image.src = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
//Draw circle on canvas
this.draw = function () {
//Circle
this.c.beginPath();
this.c.arc(this.x, this.y, (this.radius*1), 0, Math.PI * 2, false);
this.c.closePath();
this.c.save();
this.c.clip();
//TODO: cut square to circle
//Place square (image) on top of the circle
this.c.drawImage(image, (this.x-this.diameter/2) , (this.y-this.diameter/2), this.diameter, this.diameter);
this.c.restore();
};
//Update position
this.update = function () {
if(this.x - this.radius <= 0 || this.x + this.radius >= this.size.x){this.dx = -this.dx}
this.x += this.dx;
this.draw()
}
this.init = function(options) {
Object.keys(options).forEach((key)=>{
this[key]=options[key];
})
}
}
//Animate canvas
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
for(let i = 0; i < circles.length; i++){ circles[i].update(); }
}
//Start
for(let i = 0; i < 100; i-=-1){
let circle = new Circle();
circle.init({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
size: {x: window.innerWidth,y: window.innerHeight},
c
})
circles.push(circle)
}
animate();
canvas {
border: 1px solid black;
background: black;
}
body {
margin: 0;
padding: 0;
overflow-x: hidden;
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas></canvas>
</body>
</html>
Start by generating your composed image and then use it in your animation code.
To make such a compositing, you'd better use compositing than clipping, because clipping is an all or nothing operation, it won't deal well with the antialiasing required by a circle.
To store this composed image in a convenient way, you can either use a second canvas, or in newest browsers an ImageBitmap, both will then be drawable by drawImage.
Here is an ES5 implementation, using a second canvas and a callback:
// older browsers version
function makeCircleImage(radius, src, success, failure) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI*2);
ctx.fill();
success(canvas);
};
img.onerror = failure;
}
var url = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
makeCircleImage( 50, url, function( canvas ) {
document.body.appendChild(canvas);
}, console.error );
canvas {
border: 1px solid black;
background: black;
}
And here is one that you can use in newest browsers which will store an ImageBitmap (preferable when available for memory and theoretical speed of drawing).
// newest browsers version
function makeCircleImage( radius, src ) {
return new Promise( (resolve, reject) => {
// this canvas will get Garbage Collected
const canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = radius * 2;
const ctx = canvas.getContext( "2d" );
const img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage( img, 0, 0, canvas.width, canvas.height );
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc( radius, radius, radius, 0, Math.PI*2 );
ctx.fill();
resolve( createImageBitmap( canvas ) );
};
img.onerror = reject;
});
}
async function init() {
const url = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
const img = await makeCircleImage(50, url);
document.querySelector('canvas')
.getContext('2d')
.drawImage(img, 0,0);
};
init().catch( console.error );
canvas {
border: 1px solid black;
background: black;
}
<canvas></canvas>
Now that we have a clear source ready to use, all our we have to do in the animation code is to put these pixels, without any heavy computation:
// older browsers version
function makeCircleImage(radius, src, callback) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI*2);
ctx.fill();
callback(canvas);
};
}
function Circle( x, y, radius ) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
draw: function () {
var
x = (this.x - this.radius),
y = (this.y - this.radius);
// draw is a single call
c.drawImage( this.image, x, y );
},
update: function () {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
},
init: function(callback) {
var url = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
makeCircleImage( this.radius, url, function(img) {
this.image = img;
callback();
}.bind(this));
}
};
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach(function( circle ) {
circle.update();
});
circles.forEach(function( circle ) {
circle.draw();
});
requestAnimationFrame(animate);
}
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var circles = [
new Circle(10, 100, 50),
new Circle(10, 200, 30),
new Circle(10, 300, 50)
];
var ready = 0;
circles.forEach(function(circle) {
circle.init(oncircledone);
});
function oncircledone() {
if(++ready === circles.length) {
animate()
}
}
canvas {
border: 1px solid black;
background: black;
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas></canvas>
</body>
</html>
And for newest browsers:
// newest browsers version
function makeCircleImage( radius, src ) {
return new Promise( (resolve, reject) => {
// this canvas will get Garbage Collected
const canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = radius * 2;
const ctx = canvas.getContext( "2d" );
const img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage( img, 0, 0, canvas.width, canvas.height );
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc( radius, radius, radius, 0, Math.PI*2 );
ctx.fill();
resolve( createImageBitmap( canvas ) );
};
img.onerror = reject;
});
}
class Circle {
constructor( x, y, radius ) {
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
}
draw() {
const x = (this.x - this.radius);
const y = (this.y - this.radius);
c.drawImage( this.image, x, y );
}
update() {
const max_right = canvas.width + this.radius;
const max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
}
async init() {
const url = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
this.image = await makeCircleImage( this.radius, url );
}
}
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach( (circle) => circle.update() );
circles.forEach( (circle) => circle.draw() );
requestAnimationFrame(animate);
}
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const circles = [
new Circle(10, 50, 50),
new Circle(10, 150, 30),
new Circle(450, 250, 80)
];
Promise.all(circles.map( (circle) => circle.init() ))
.then(animate)
.catch(console.error);
canvas {
border: 1px solid black;
background: black;
}
<canvas></canvas>
But if you are going to have a lot of Circle instances and that they will only differ in their radius, but share the same original image's source, then you might prefer to store a single high quality version of the composed image and just rescale it in each instance's draw call.
// newest browsers version
function makeCircleImage( radius, src ) {
return new Promise( (resolve, reject) => {
// this canvas will get Garbage Collected
const canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = radius * 2;
const ctx = canvas.getContext( "2d" );
const img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage( img, 0, 0, canvas.width, canvas.height );
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc( radius, radius, radius, 0, Math.PI*2 );
ctx.fill();
resolve( createImageBitmap( canvas ) );
};
img.onerror = reject;
});
}
(async () => {
const url = "https://assets.coingecko.com/coins/images/2607/large/molecular_future.png?1547036754";
const image = await makeCircleImage(250, url); // original size of the png image
class Circle {
constructor( x, y, radius ) {
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
}
draw() {
const x = (this.x - this.radius);
const y = (this.y - this.radius);
// now they all use the same image
// with a bit more computation though due to resizing
c.drawImage( image, x, y , this.radius * 2, this.radius * 2);
}
update() {
const max_right = canvas.width + this.radius;
const max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
}
}
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach( (circle) => circle.update() );
circles.forEach( (circle) => circle.draw() );
requestAnimationFrame(animate);
}
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const rand = (n) => Math.random() * n;
const circles = Array.from({ length: 50 })
.map( () =>
new Circle(
rand(canvas.width),
rand(canvas.height),
rand(125)
)
);
animate();
})()
.catch(console.error);
canvas {
border: 1px solid black;
background: black;
}
<canvas></canvas>

Detecting circle intersection from array

I have a canvas on which I can place circles wherever I click. I want to detect when any two circle intersect, so i am storing my coordinates in an array.
The radius of every circle is 30, so that is just hardcoded into my formula. That said, even when I place two on top of each other, it's not triggering my little filltext to let me know that it's working. I've tried many things. If someone could tell me why this isn't working, that would be appreciable. The parts where I place the dots works just fine; I just need to detect overlap.
window.onload = init;
function init() {
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var newPath = false;
var circles = [];
canvas.onmousedown = function(e) {
newPath = true;
x = e.clientX - e.target.offsetLeft;
y = e.clientY - e.target.offsetTop;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, 30, 0, 2 * Math.PI, true);
var nextColor = randomColor();
context.fillStyle = nextColor;
context.fill();
var aCircle = [x, y];
function isIntersect(aCircle, circle) {
return Math.sqrt((aCircle[0]-circle.x) ** 2 + (aCircle[1] - circle.y) ** 2) < 30;
};
circles.forEach(circle => {
if (isIntersect(aCircle, circle)) {
context.fillText('INTERSECTED', 60, 160);
}
});
circles.push(aCircle);
context.closePath();
}
}
Multiply the radius by 2 since each circle has one...
window.onload = init;
function init() {
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var newPath = false;
var circles = [];
canvas.onmousedown = function(e) {
newPath = true;
x = e.clientX - e.target.offsetLeft;
y = e.clientY - e.target.offsetTop;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, 30, 0, 2 * Math.PI, true);
var nextColor = '#123123' //randomColor();
context.fillStyle = nextColor;
context.fill();
var aCircle = [x, y];
function isIntersect(aCircle, circle) {
var radius = 30;
var dist = Math.hypot(aCircle[0]-circle[0], aCircle[1]-circle[1]);
return dist <= (radius * 2)
};
circles.forEach(circle => {
if (isIntersect(aCircle, circle)) {
console.log("intresected");
//context.fillText('INTERSECTED', 0, 0);
}
});
circles.push(aCircle);
context.closePath();
}
}
<canvas id='testCanvas'></canvas>

Loop Background Image Animation in Canvas

I am new to Canvas and want to loop background Image in the below smoke effect. On searching, I have found an example that how we can loop background Image in canvas Link to looping animation so I tried integrating the looping code with the smoke effect but no success. Any help will be appreciated.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
context.drawImage(backImg, 0, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-lights';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas" ></canvas>
I just added a few lines. Hopefully you can spot them. I commented everything I added.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
// x position of scrolling image
var imageX = 0;
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
// draw twice to cover wrap around
context.drawImage(backImg, imageX, 0, canvasWidth, canvasHeight);
context.drawImage(backImg, imageX + canvasWidth, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-light';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
// incrementally change image position of background to scroll left
imageX -= maxVelocity;
if (imageX < -canvasWidth) {
imageX += canvasWidth;
}
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas"></canvas>
Just to add to the answer given some additional improvements to the code structure.
Use requestAnimationFrame to call render calls.
Don't expose properties of objects if not needed.
Don't use forEach iteration in time critical code. Use for loops.
Use constants where ever possible.
Comments that state the obvious are just noise in the source code making it harder to read. Limit comment to abstracts that may not be obvious to another programmer reading the code.
eg
// If an image is set draw it
if (this.image) {
Really is that comment of any use to anyone. Comments should help not degrade the readability of code.
Also the original code tried to set the global composite operations to soft-lights this is not a know operation. I corrected it to soft-light which can on some machines, be a very slow render operation. It may pay to selected another operation for machines that are slow. This can be done by simply monitoring the render time of particles and switching operation type is too slow.
A quick rewrite of the OP's code.
const particles = [];
const particleCount = 60;
const maxVelocity = 2;
var canvasWidth = innerWidth;
var canvasHeight = innerHeight;
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
var ctx;
const backgroundColor = "rgba(255,255,255, .5)";
const backgroundSpeed = -0.1;
var looping = false;
var totalSeconds = 0;
var lastTime = 0;
var frameTime = (1000 / 30) - (1000 / 120); // one quater frame short to
// allow for timing error
var imageCount = 0;
const backImg = new Image();
const imageObj = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
backImg.onload = imageObj.onload = imageLoad;
function imageLoad(){
imageCount += 1;
if(imageCount === 2){
init();
}
}
function init() {
var canvas = myCanvas;
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext('2d');
for (var i = 0; i < particleCount; i += 1) {
particles.push(new Particle(ctx));
}
lastTime = performance.now();
requestAnimationFrame(mainLoop);
}
function mainLoop(time){
if(time-lastTime > frameTime){
lastTime = time;
update();
draw(time);
}
requestAnimationFrame(mainLoop);
}
const rand = (min, max) => Math.random() * (max - min) + min; // names are best short (short only without ambiguity)
function Particle(ctx) {
var x, y, xVel, yVel, radius, image;
const color = "rgba(0, 255, 255, 1)";
x = rand(0, canvasWidth),
y = rand(borderTop, borderBottom);
xVel = rand(-maxVelocity, maxVelocity);
yVel = rand(-maxVelocity, maxVelocity);
radius = 5;
image = imageObj;
this.draw = function () { ctx.drawImage(image, x - 128, y - 128) }
this.update = function () {
x += xVel;
y += yVel;
if (x >= canvasWidth) {
xVel = -xVel;
x = canvasWidth;
}
else if (x <= 0) {
xVel = -xVel;
x = 0;
}
if (y >= borderBottom) {
yVel = -yVel;
y = borderBottom;
}
else if (y <= borderTop) {
yVel = -yVel;
y = borderTop;
}
}
}
function draw(time) {
var i,x;
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
x = time * backgroundSpeed;
x = ((x % canvasWidth) + canvasWidth) % canvasWidth;
ctx.drawImage(backImg, x, 0, canvasWidth, canvasHeight);
ctx.drawImage(backImg, x - canvasWidth, 0, canvasWidth, canvasHeight);
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.globalAlpha = 0.75;
ctx.globalCompositeOperation = 'soft-light';
for(i = 0; i < particles.length; i += 1){
particles[i].draw();
}
}
function update() {
for(i = 0; i < particles.length; i += 1){
particles[i].update();
}
}
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=myCanvas></canvas>

Categories

Resources