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>
Related
let canvas, ctx, W, H;
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
let objArr = [];
const init = () => {
canvas.width = W = innerWidth;
canvas.height = H = innerHeight;
canvas.style.background = '#dedfda';
for (let i = 0; i <= 5; i++) {
let r = (i + 1) * 20;
objArr.push(new Rectangle(W / 2, H / 2, 10, 10, "red", innerWidth / 2, innerHeight / 2, r));
objArr[0].draw();
}
animate();
}
const random = (min = 0, max = 1) => Math.random() * (max - min) + min;
class Rectangle {
constructor(posX, posY, w, h, color, cx, cy, r) {
this.posX = posX;
this.posY = posY;
this.w = w;
this.h = h;
this.color = color;
this.cx = cx;
this.cy = cy;
this.r = r;
this.angle = 0;
}
draw() {
ctx.clearRect(0, 0, innerWidth, innerHeight);
ctx.fillStyle = this.color;
ctx.fillRect(this.posX, this.posY, this.w, this.h);
}
update() {
this.angle += 0.1;
this.posX = this.cx + this.r * Math.cos(this.angle);
this.posY = this.cy + this.r * Math.sin(this.angle);
this.draw();
}
}
const animate = () => {
objArr.forEach(e => {
e.update();
})
requestAnimationFrame(animate);
}
window.onload = init;
In this code, I'm expecting an output having 5 rectangles revolving around the center point of the window in different radius like the planets revolving around the Sun in solar system.
But I am getting only one rectangle and it's not even revolving.
Stucked and not getting why it's not working.
Your draw function is clearing every time and you are only referencing the first element of the array. Adjusting for both shows 6 objects revolving. Your loop runs from 0 to 5. If you want 5 objects, you'll need to set the upper bound to 4 instead.
let canvas, ctx, W, H;
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
let objArr = [];
const init = () => {
canvas.width = W = innerWidth;
canvas.height = H = innerHeight;
canvas.style.background = '#dedfda';
// adjusted range
for (let i = 0; i <= 4; i++) {
let r = (i + 1) * 20;
objArr.push(new Rectangle(W / 2, H / 2, 10, 10, "red", innerWidth / 2, innerHeight / 2, r));
objArr[i].draw();
}
animate();
}
const random = (min = 0, max = 1) => Math.random() * (max - min) + min;
class Rectangle {
constructor(posX, posY, w, h, color, cx, cy, r) {
this.posX = posX;
this.posY = posY;
this.w = w;
this.h = h;
this.color = color;
this.cx = cx;
this.cy = cy;
this.r = r;
this.angle = 0;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.posX, this.posY, this.w, this.h);
}
update() {
this.angle += 0.1;
this.posX = this.cx + this.r * Math.cos(this.angle);
this.posY = this.cy + this.r * Math.sin(this.angle);
this.draw();
}
}
const animate = () => {
// moved clear here to happen just once on each animate
ctx.clearRect(0, 0, innerWidth, innerHeight);
objArr.forEach(e => {
e.update();
})
requestAnimationFrame(animate);
}
window.onload = init;
You are just drawing one rectangle. Pay attention to
objArr[0].draw()
Which should be
objArr[i].draw()
Also, I think everytime an Rectange object's draw method is called the whole canvas is cleared in this line
ctx.clearRect(0, 0, innerWidth, innerHeight);
Hence, only one rectangle appears at all times.
A solution would be to move the calls to clearRect and update from Rectangle to the animate function. This way it fits better with the program logic. A Rectangle being an object should only know how to draw itself not clearing the canvas and updating its position on the canvas.
Below is my solution.
let canvas, ctx, W, H;
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
let objArr = [];
const init = () => {
canvas.width = W = innerWidth;
canvas.height = H = innerHeight;
canvas.style.background = '#dedfda';
for (let i = 0; i <= 5; i++) {
let r = (i + 1) * 20;
objArr.push(new Rectangle(W / 2, H / 2, 10, 10, "red", innerWidth / 2, innerHeight / 2, r));
objArr[i].draw();
}
animate();
}
const random = (min = 0, max = 1) => Math.random() * (max - min) + min;
class Rectangle {
constructor(posX, posY, w, h, color, cx, cy, r) {
this.posX = posX;
this.posY = posY;
this.w = w;
this.h = h;
this.color = color;
this.cx = cx;
this.cy = cy;
this.r = r;
this.angle = 0;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.posX, this.posY, this.w, this.h);
}
// Should be moved outside of this class.
update() {
this.angle += 0.1;
this.posX = this.cx + this.r * Math.cos(this.angle);
this.posY = this.cy + this.r * Math.sin(this.angle);
ctx.fillRect(this.posX, this.posY, this.w, this.h)
}
}
const animate = () => {
ctx.clearRect(0, 0, innerWidth, innerHeight);
objArr.forEach(e => {
e.update();
})
requestAnimationFrame(animate);
}
window.onload = init;
I was successful in generating enemies (out of one image) with an array, however, I'm stuck in trying to generate enemies out of more than one image (e. g. 5 different images, thus 5 different enemies)
Here is my code that works:
Enemies (enemyImg - one image) are generated
/** #type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const backgroundImg = document.getElementById("background");
const playerImg = document.getElementById("player");
const enemyImg = document.getElementById("enemy");
canvas.width = 800;
canvas.height = 500;
const enemies = [];
class Enemy {
constructor(x, y, w, h, speed) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speed = speed;
}
draw() {
ctx.drawImage(enemyImg, this.x, this.y, this.w, this.h);
}
update() {
this.x = this.x - this.speed;
}
}
function spawnEnemies() {
setInterval(() => {
let w = 100;
let h = 40;
let x = canvas.width;
let y = Math.random() * Math.abs(canvas.height - h);
let speed = 5;
enemies.push(new Enemy(x, y, w, h, speed));
}, 1000);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
enemies.forEach((enemy) => {
enemy.draw();
enemy.update();
});
}
animate();
spawnEnemies();
Here is the code, that does not work. I do not get any error message at all:
I have 6 images in one folder, named enemy_1.png to enemy_6.png) and I want them to be generated;
/** #type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const backgroundImg = document.getElementById("background");
const playerImg = document.getElementById("player");
const enemyImg = document.getElementById("enemy");
canvas.width = 800;
canvas.height = 500;
let enemies = [];
class Enemy {
constructor(img, x, y, w, h, speed) {
this.img = img;
(this.x = x),
(this.y = y),
(this.w = w),
(this.h = h),
(this.speed = speed);
}
draw() {
ctx.drawImage(img, this.x, this.y, this.w, this.h);
}
move() {
this.x = this.x - this.speed;
}
}
function createEnemies() {
setInterval(() => {
let w = 40;
let h = 72;
let x = 300;
let y = Math.random() * Math.abs(canvas.height - h);
let speed = 5;
enemies.length = 6;
for (let i = 1; i < enemies.length; i++) {
enemies[i] = new Image();
enemies[i].src = "./images/enemy_" + i + ".png";
enemies.push(new Enemy(enemies[i], x, y, w, h, speed));
}
}, 2000);
}
function createGame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
enemies.forEach((enemy) => {
enemy.draw();
enemy.move();
});
requestAnimationFrame(createGame);
}
createGame();
createEnemies();
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const backgroundImg = document.createElement('img');
const playerImg = document.createElement('img');
canvas.width = 500;
canvas.height = 200;
// load your images:
const imagesCount = 0; // I have not this images, so its zero for me
const enemyImages = [];
for (let i = 1; i < imagesCount; i++) {
const img = new Image();
img.src = "./images/enemy_" + i + ".png";
enemyImages.push(img);
}
// I have not your images so i take some random pictures:
const enemyImage1 = new Image();
enemyImage1.src = 'https://pngimg.com/uploads/birds/birds_PNG106.png';
const enemyImage2 = new Image();
enemyImage2.src = 'https://purepng.com/public/uploads/large/purepng.com-magpie-birdbirdsflyanimals-631522936729bqeju.png';
const enemyImage3 = new Image();
enemyImage3.src = 'https://www.nsbpictures.com/wp-content/uploads/2018/10/birds-png.png';
enemyImages.push(
enemyImage1,
enemyImage2,
enemyImage3,
);
const enemies = [];
class Enemy {
constructor(x, y, w, h, speed, img) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speed = speed;
// Self image:
this.img = img;
}
draw() {
// Draw self image:
ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
}
update() {
this.x = this.x - this.speed;
}
}
function spawnEnemies() {
setInterval(() => {
let w = 60;
let h = 50;
let x = canvas.width;
let y = Math.random() * Math.abs(canvas.height - h);
let speed = 5;
enemies.push(new Enemy(x, y, w, h, speed,
// Pick random image from array:
enemyImages[Math.floor(Math.random()*enemyImages.length)]
));
}, 1000);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
enemies.forEach((enemy) => {
enemy.draw();
enemy.update();
});
}
animate();
spawnEnemies();
<canvas id=canvas></canvas>
I am trying to make an application with canvas. But I need to delete a drawing I created in my application. I want the red to stay steady and the blue to reflect as I hold it down. But while cleaning the canvas, the previous blue drawings are also gone. How should I go about deleting a drawing? I also tried throwing it into an array and deleting it with remove() but it didn't work. If I delete it with its coordinates, all the drawings in those coordinates will also disappear.
const canvas = document.getElementById('canvas-pl-c-canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
top() {
c.beginPath();
c.arc(this.x, this.y, 10, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update(nx, ny) {
this.x = nx;
this.y = ny;
}
}
const x = canvas.width / 2;
const y = canvas.height / 2;
var player = new Player(x, y, 30,'red');
player.top();
addEventListener('mousemove', (e) => {
c.clearRect(0, 0, canvas.width, canvas.height)
player.update(e.clientX, e.clientY);
player.top();
if(clck == true){
var wd = new Player(e.clientX, e.clientY, 300,'blue');
wd.top();
}
})
var clck=false;
canvas.addEventListener('mouseup', (e) => {
clck=false;
})
canvas.addEventListener('mousedown', (e) => {
clck=true;
})
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas-pl-c-canvas:hover{
cursor: none;
}
<canvas id="canvas-pl-c-canvas"></canvas>
In the above snippet I'm trying to get the blue to draw without the red disappearing, but it doesn't work because the entire area is cleared with c.clearRect(0, 0, canvas.width, canvas.height). Is this the only way to clean it? How can I delete one of two overlapping drawings? How can I save my drawings somewhere and delete them?
const canvas = document.getElementById('canvas-pl-c-canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// ctx.fillStyle='blue';
// ctx.fillRect(0,0,canvas.width,canvas.height)
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
top() {
c.beginPath();
c.arc(this.x, this.y, 10, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update(nx, ny) {
this.x = nx;
this.y = ny;
}
}
const x = canvas.width / 2;
const y = canvas.height / 2;
var player = new Player(x, y, 30,'red');
player.top();
addEventListener('mousemove', (e) => {
// c.clearRect(0, 0, canvas.width, canvas.height)
player.update(e.clientX, e.clientY);
player.top();
if(clck == true){
var wd = new Player(e.clientX, e.clientY, 300,'blue');
wd.top();
}
})
var clck=false;
canvas.addEventListener('mouseup', (e) => {
clck=false;
})
canvas.addEventListener('mousedown', (e) => {
clck=true;
})
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas-pl-c-canvas:hover{
cursor: none;
}
<canvas id="canvas-pl-c-canvas"></canvas>
In this piece of code, I can draw blue as long as I hold it down, but it constantly writes red. I specially need to delete a drawing. I'm using completely vanilla js. I would be very grateful if you could help without suggesting a library. Thank you very much to everyone who has replied so far.
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>
This works great on all of my tests, but on the live system, I have a problem.
There are three separate but identical canvases. They are all initially painted with 640x480 jpg images. The third is then updated every second with a new 640x480 jpg image.
For some reason, the original static image for the third canvas - and that canvas only - doesn't load from the live server, only on the tests.
Here's the html:
<div style="padding: 1vw;">
<a href="/virtualscribe" class="camLink">
<div class="camDiv">
<div class="camBox" id="picBoxLeft">
<canvas id="canvasLeft" class="camBorder"></canvas>
</div>
</div>
</a>
<a href="/videos" class="camLink">
<div class="camDiv">
<div class="camBox" id="picBoxCenter">
<canvas id="canvasCenter" class="camBorder"></canvas>
</div>
</div>
</a>
<div class="camDiv">
<div class="camBox" id="picBoxRight">
<canvas id="canvasRight" class="camBorder"></canvas>
</div>
</div>
</div>
Here's the CSS:
<style>
.camBox {
max-width: 28vw;
width: 28vw;
height: 21vw;
max-height: 21vw;
display: inline-block;
}
.camDiv {
padding: 1.5vw;
display: inline-block;
}
.camBorder {
border: 0.6vw solid #fe890f;
border-top-left-radius: 3.3vw;
border-bottom-right-radius: 3.3vw;
}
.camLink {
color: transparent;
}
</style>
And here's the JavaScript:
var refreshTimer;
var imgLiveCam;
function drawPictureBox(img, canvas, text) {
var context = canvas.getContext("2d");
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.globalAlpha = 0.6;
context.fillStyle = "rgb(244,121,32)";
context.fillRect(0, canvas.height * 0.67, canvas.width, canvas.height * 0.33);
context.globalAlpha = 1;
var maxWidth = canvas.width;
var x = canvas.width / 2;
var y = canvas.height * 0.85;
context.font = canvas.width / 12 + 'px Oswald';
context.strokeStyle = 'black';
context.lineWidth = 2;
context.textAlign = "center";
context.strokeText(text, x, y, maxWidth);
context.fillStyle = 'white';
context.fillText(text, x, y, maxWidth);
};
function drawCamBox(img, logo, canvas) {
var context = canvas.getContext("2d");
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.globalAlpha = 0.6;
context.fillStyle = "rgb(244,121,32)";
context.fillRect(0, canvas.height * 0.67, canvas.width, canvas.height * 0.33);
var eSize = canvas.height / 10;
context.globalAlpha = 1;
context.drawImage(logo, 2, canvas.height * 0.67 - eSize, eSize, eSize);
var now = new Date();
var text = now.toLocaleDateString() + " " + now.toLocaleTimeString();
var maxWidth = canvas.width * 0.33;
var x = canvas.width - 10 - maxWidth;
var y = canvas.height * 0.67 - 10;
context.font = maxWidth / 10 + "px Arial";
context.strokeStyle = 'black';
context.lineWidth = 2;
context.strokeText(text, x, y, maxWidth);
context.fillStyle = 'white';
context.fillText(text, x, y, maxWidth);
text = 'View Our Production Floor';
context.textAlign = "center";
maxWidth = canvas.width;
x = canvas.width / 2;
y = canvas.height * 0.85;
context.font = canvas.width / 12 + 'px Oswald';
context.strokeText(text, x, y, maxWidth);
context.fillText(text, x, y, maxWidth);
}
function addResizeListener(fn) {
if (window.attachEvent) {
window.attachEvent('onresize', fn)
} else if (window.addEventListener) {
window.addEventListener('resize', fn, true);
};
}
function initCam() {
var imgLeft = new Image();
imgLeft.onload = function () {
var canvas = document.getElementById("canvasLeft");
var picBox = document.getElementById("picBoxLeft");
var drawFn = function () {
canvas.height = picBox.clientHeight;
canvas.width = picBox.clientWidth;
drawPictureBox(imgLeft, canvas, 'Looking for a Scribe?');
};
drawFn();
addResizeListener(drawFn);
}
var imgCenter = new Image();
imgCenter.onload = function () {
var canvas = document.getElementById("canvasCenter");
var picBox = document.getElementById("picBoxCenter");
var drawFn = function () {
canvas.height = picBox.clientHeight;
canvas.width = picBox.clientWidth;
drawPictureBox(imgCenter, canvas, 'Watch In-Depth Videos');
};
drawFn();
addResizeListener(drawFn);
}
imgLiveCam = new Image();
var logo = new Image();
logo.src = "e.png";
imgLiveCam.onload = function () {
var canvas = document.getElementById("canvasRight");
var picBox = document.getElementById("picBoxRight");
var drawFn = function () {
canvas.height = picBox.clientHeight;
canvas.width = picBox.clientWidth;
drawCamBox(imgLiveCam, logo, canvas);
};
drawFn();
imgLiveCam.onload = drawFn;
addResizeListener(drawFn);
}
imgLeft.src = "scribe.jpg";
imgCenter.src = "doc-with-spine.jpg";
imgLiveCam.src = "camloading.jpg";
refreshTimer = setTimeout(refresh, 499);
}
function refresh() {
clearTimeout(refreshTimer);
imgLiveCam.src = "http://cams.edataservices.com/17fcam.jpg?t=" + new Date().getTime();
refreshTimer = setTimeout(refresh, 1009);
}
Any idea why the third canvas starts out blank most of the time?
By adding a longer delay before starting the refresh cycle, the browsers had more time to load the initial, "default" image. I set it at 1500ms, and now it is pretty reliable.
(Thanks for the help...)