I want to draw many different sized circles with the same image on them using a prototype. The problem is, the circles don't appear with the images drawn on them.
The circles are still there because they're visible when stroke is used, but the images aren't drawn. I was able to have an arc be drawn with an image in it without the prototype but as soon as I use it, it doesn't work.
No bugs appear in the console, so it is not clear to me what I'm missing.
I've tried moving the event listener outside the prototype but the circle images still did not appear.
If anyone has any insight and perhaps a solution as to why this isn't working that they can share, that would be very much appreciated.
Here is the code:
const ctx = document.getElementById('canvas').getContext('2d');
class Balls {
constructor(xPos, yPos, radius){
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
}
}
Balls.prototype.render = function(){
const img = document.createElement('img');
img.src = 'crystal.jpg';
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI*2)
ctx.stroke();
ctx.clip();
img.addEventListener('onload', function(e){
ctx.drawImage(img, this.xPos - this.radius, this.yPos - this.radius,
this.radius*2, this.radius*2);
});
};
let object = new Balls(100, 100, 50);
object.render();
<canvas id='canvas'></canvas>
Here you go, i just changed 2 things here in order to make your code work.
load is used for eventlistener instead on onload.
use Arrow function in order to rely on the parent context instead of dynamic context(this).
const ctx = document.getElementById('canvas').getContext('2d');
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
}
}
Balls.prototype.render = function() {
const img = document.createElement('img');
img.src = 'https://via.placeholder.com/150.png?text=vijay'
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2)
ctx.stroke();
ctx.clip();
img.addEventListener('load', (e) => {
ctx.drawImage(img, this.xPos - this.radius, this.yPos - this.radius,
this.radius * 2, this.radius * 2);
});
};
let object = new Balls(100, 100, 50);
object.render();
<canvas id='canvas'></canvas>
You had two issues in your code. Firstly, the onload event should be load. Secondly, this is not known inside the event listener function, so this.radius etc. are all undefined and ctx.drawImage(img, this.xPos - this.radius, this.yPos - this.radius,
this.radius*2, this.radius*2) doesn't work.
I used some variables for this.xPos, this.yPos, this.radius that can be used inside the event listener callback function.
Hope this helps!
const ctx = document.getElementById('canvas').getContext('2d');
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
}
}
Balls.prototype.render = function() {
const img = new Image();
img.src = 'https://source.unsplash.com/random';
var xPos = this.xPos, yPos = this.yPos, radius = this.radius
ctx.arc(xPos, yPos, radius, 0, Math.PI * 2)
ctx.stroke();
ctx.clip();
img.addEventListener('load', function(e) {
console.log("load")
ctx.drawImage(img, xPos - radius, yPos - radius,
radius*2, radius*2);
});
};
let object = new Balls(100, 100, 50);
object.render();
<canvas id='canvas'></canvas>
This is an alternative way you can load images which is more modular, If you are making a game an Img class will come in handy.
Off-topic -
if you want to load multiple images then you can add atotalLoadedcounter to keep track of how many images has been loaded and if thetotalLoadedcount is equal tototalImagescount then you can call a callback which will fire the animationLoop and preloads all the images.
const ctx = document.getElementById('canvas').getContext('2d');
// Img class to provide modular code
class Img {
constructor(src, x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.src = src;
this.img = new Image();
this.img.src = this.src;
this.isLoaded = false;
this.img.addEventListener('load', (e) => {
this.isLoaded = true;
this.draw();
});
}
draw() {
if (this.isLoaded) {
ctx.drawImage(this.img, this.x, this.y, this.size, this.size);
}
}
}
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
}
}
Balls.prototype.render = function() {
let imgx = this.xPos - this.radius;
let imgy = this.yPos - this.radius;
let imgr = this.radius * 2;
let url = 'https://via.placeholder.com/150.png?text=Better';
const img = new Img(url, imgx, imgy, imgr);
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2)
ctx.stroke();
ctx.clip();
img.draw();
};
let object = new Balls(100, 100, 50);
object.render();
<canvas id='canvas'></canvas>
Related
Solved Had to render the images globally outside of the draw method. Here's a link to the github if you find this question and are wondering what the solution looks like in the full code. Its a bit too long to post here as an update. github canvas orbs
back to the original question:
I'm trying to render custom Class objects inside an HTML canvas. Doesn't work with class, but the the same data works without class.
Here's the code:
import './styles/index.css';
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
var window_height = window.innerHeight;
var window_width = window.innerWidth;
canvas.width = 500;
canvas.height = 400;
canvas.style.background = "#232a2e"
const convertSVG = (svgid) => {
const svg = document.getElementById(svgid);
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = btoa(xml);
const b64Start = 'data:image/svg+xml;base64, ';
return b64Start + svg64;
}
class Orb {
constructor(xpos, ypos, radius, speed, image) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.speed = speed;
this.image = convertSVG(image);
}
draw(context) {
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (this.xpos - this.radius), (this.ypos - this.radius), 64, 64);
context.restore();
}
img.src = this.image;
}
}
const myOrb = new Orb(150, 150, 30, 1, 'javascript-icon');
myOrb.draw(context);
console.log(myOrb);
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(300, 300, 30, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (300-32), (300-32), 64, 64);
context.restore();
}
img.src = convertSVG('javascript-icon');
Currently it's only displaying the object I draw explicitly at the bottom of the code, and not the object of class Orb.
Here's a screen cap of the canvas:
current canvas render
EDIT: Additionally, I can generate a class object and draw the orb based on that. Like so:
const myOrb = new Orb(300, 300, 30, 1, 'javascript-icon');
const myOrb2 = new Orb(150, 150, 30, 1, 'java-icon');
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(myOrb.xpos, myOrb.ypos, myOrb.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (myOrb.xpos-myOrb.radius-2), (myOrb.ypos-myOrb.radius-2), 64, 64);
context.restore();
}
img.src = myOrb.imageSRC;
console.log(myOrb);
console.log(myOrb2);
myOrb2.draw(context);
myOrb renders, but myOrb2 which is drawn with the method of the class is not rendered.
Here's an approach to take. Load the images globally and call draw when image is loaded.
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
var window_height = window.innerHeight;
var window_width = window.innerWidth;
canvas.width = 500;
canvas.height = 400;
canvas.style.background = "#232a2e"
const img1 = new Image();
img1.src = 'https://cdn.iconscout.com/icon/free/png-256/javascript-2752148-2284965.png';
const img2 = new Image();
img2.src = 'https://iconape.com/wp-content/png_logo_vector/cib-javascript.png'
class Orb {
constructor(xpos, ypos, radius, speed, image) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.speed = speed;
this.image = image;
}
draw(context) {
context.save();
context.beginPath();
context.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(this.image, (this.xpos - this.radius), (this.ypos - this.radius), 64, 64);
context.restore();
}
}
const myOrb = new Orb(150, 150, 30, 1, img1);
const myOrb2 = new Orb(350, 150, 30, 1, img2);
window.onload = function() {
myOrb.draw(context);
myOrb2.draw(context);
}
<canvas id="canvas"></canvas>
I want a ball falling, with gravity. But the background won't clear the previous circle. (the falling part isnt implemented yet) why doesnt it erase the last drawn object?
let c, ctx;
let met;
let colorArray = ["green", "yellow", "orange", "red"];
window.onload = function(){
c = document.getElementById("boom");
ctx = c.getContext("2d");
c.width = window.innerWidth - 165;
c.height = window.innerHeight;
setup();
draw();
}
function setup(){
met = new Meteor(200, 400, 10, 0, 5);
}
function draw(){
window.requestAnimationFrame(draw);
ctx.fillStyle = colorArray[Math.floor(Math.random() * colorArray.length)];
ctx.fillRect(0, 0, c.width, c.height);
met.draw();
met.fall();
};
class Meteor {
constructor(x, y, r, xSpeed, ySpeed){
this.xPos = x;
this.yPos = y;
this.radius = r;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.color = "blue";
this.acceleration = 0.5;
}
draw(){
ctx.fillStyle = this.color;
ctx.arc(this.xPos, this.yPos, this.radius, 0, 2 * Math.PI);
//ctx.fill();
ctx.stroke();
}
fall(){
this.yPos += (this.ySpeed);
}
}
Also, if you have any tips on how to make it have gravity, please don't hesitate to tell me. Ive been struggling with it for a while now.
In draw() add this before the call to met.draw():
ctx.clearRect(0, 0, window.innerWidth, window.innterHeight)
This will clear the screen every frame.
For gravity, add a dt parameter to your draw function, then pass it to met.draw() like this:
function draw(dt){
window.requestAnimationFrame(draw);
ctx.fillStyle = colorArray[Math.floor(Math.random() * colorArray.length)];
ctx.fillRect(0, 0, c.width, c.height);
ctx.clearRect(0, 0, window.innerWidth, window.innterHeight) // Clear screen here
met.draw(dt);
met.fall();
};
Then in the Meteor class, use the dt in the draw() function to calculate your y position based on velocity. Here dt will be your delta time for the calculation.
This question already has answers here:
clearRect function doesn't clear the canvas
(5 answers)
Closed 4 years ago.
I'm creating a canvas game that has balls that bounce off each other.
I would like the balls to have their own skins where a background image is put onto the arc element.
When the ball isn't bouncing, the image clips just fine and is circular, like the arc. However, when I start to animate the ball, it simply wouldn't move at all because the clip function doesn't allow the image or the arc to be redrawn.
That's when I discovered the save and restore functions in the canvas that have allowed the clip method to be used while animating.
The issue with this is that part of the image doesn't clip properly. Only half of the animation is circular and the other half is the rectangular image. I've tried adjusting the position of the image but this did not lead to the desired result.
I'm not sure why the code is behaving like this and how to fix it so that it's simply an animating ball with an image background.
If someone has some insight into this and perhaps a solution, that would be very much appreciated.
Here is the code and a snippet below:
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
ctx.save();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
ctx.restore();
}
setInterval(animate, 50);
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src='practice.js'></script>
<link rel="stylesheet" type="text/css" href="practice.css">
</head>
<body>
<canvas id="canvas" height="200" width="800" />
</body>
</html>
You need to call ctx.beginPath(), otherwise, every call to arc() are added to the same and only sub-path. This means that at the end, you are clipping a weird Path made from a lot of arcs: ASCII representation of the Path after 5 frames : ((((( )
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
ctx.save();
// begin a new sub-path
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
ctx.restore();
requestAnimationFrame(animate);
}
animate();
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />
Also note that for circle masking, you would be better to use compositing than clipping, compositing handles better antialiasing, and doesn't require expensive save/restore.
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
// begin a new sub-path
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
ctx.globalCompositeOperation = 'source-over';
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
requestAnimationFrame(animate);
}
animate();
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />
Im trying to create and delete arcs on events, to adding parts works fine and i'm saving them in arrays so i could delete them on calling an event listener by somehow that's not happening , I mean its working fine in the console as in the array values are decremented by its not updating in the canvas
Code:
<script>
var myCanvas = document.getElementById("myCanvas");
myCanvas.width = window.innerWidth;
myCanvas.height = 500;
var c = myCanvas.getContext("2d");
var myArr = [];
myCanvas.addEventListener("click", function(){
var x = event.x;
var y = event.y;
var radius = 10;
myArr.push(new CreateCircle(x, y, radius, "#34495e"));
console.log( myArr );
});
window.addEventListener('keydown', function(){
myArr.splice(0,1);
console.log(myArr);
});
function CreateCircle(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI*2);
c.fillStyle = this.color;
c.fill();
}
this.draw();
}
</script>
Do i need to add an delete function in the constructor function and call it on keydown event , how do i go on doing it/fixing it ?
To remove the circles, you have to clear the canvas, and then redraw it with the modified array of circles.
First of all, return an object from the CreateCircle method, so you have something to work with. There's no need for instances here.
Secondly, you could clear the canvas by resetting it's width, and then redraw based on the array, like this
var myCanvas = document.getElementById("myCanvas");
myCanvas.width = window.innerWidth;
myCanvas.height = 500;
var c = myCanvas.getContext("2d");
var myArr = [];
myCanvas.addEventListener("click", function() {
var x = event.x;
var y = event.y;
var radius = 10;
myArr.push(CreateCircle(x, y, radius, "#34495e"));
});
window.addEventListener('keydown', function() {
myArr.splice(0, 1);
myCanvas.width = myCanvas.width;
myArr.forEach(function(circle) {
CreateCircle(circle.x, circle.y, circle.r, circle.c)
})
});
function CreateCircle(x, y, radius, color) {
c.beginPath();
c.arc(x, y, radius, 0, Math.PI * 2);
c.fillStyle = color;
c.fill();
return {x: x, y: y, r: radius, c: color};
}
<canvas id="myCanvas"></canvas>
You don't update canvas anywhere. You need to create some sort of "render" function which will clear previously rendered frame and then loops through circles in array and call .draw on all of them.
Tip:
context.clearRect method is useful for clearing canvas.
Can somebody explain me why this code doesn't work? It's supposed to draw two paddles and a ball (rectangular) in a canvas so far. I'm new in JavaScript, and I'm following this YouTube tutorial, but I got stuck almost at the very beginning. Besides not knowing why it doesn't draw all the elements of the game I also don't understand the meaning of the var = loop function inside the main function. Help please!!
var WIDTH=700;
var HEIGHT=500;
var pi=Math.PI;
var canvas;
var ctx;
var keystate;
var player;
var ai;
var ball;
player = {
x: null,
y: null,
width: 20,
height: 100,
update: function(){},
draw: function (){
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
ai = {
x: null,
y: null,
width: 20,
height: 100,
update: function(){},
draw: function (){
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
ball = {
x: null,
y: null,
side: 20,
update: function(){},
draw: function (){
ctx.fillRect(this.x, this.y, this.side, this.side);
}
}
function main(){
canvas = document.createElement("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
init();
var loop = function(){
update();
draw();
window.requestAnimationFrame(loop,canvas);
};
window.requestAnimationFrame(loop,canvas);
}
function init(){
player.x = player.width;
player.y = (HEIGHT - player.height)/2;
ai.x = WIDTH - (player.width + ai.width);
ai.y = (HEIGHT - ai.height)/2;
ball.x = (HEIGHT - ball.side)/2;
ball.y = (HEIGHT - ball.side)/2;
}
function update(){
ball.update();
player.update();
ai.update();
}
function draw(){
ctx.fillRect(0,0,WIDTH,HEIGHT);
ctx.fillStyle = "#fff";
ball.draw();
player.draw();
ai.draw();
}
main();
</script>
You are drawing everything in white.
That's why you see nothing. The background is white and the shapes you draw are as well.
Try
function draw(){
ctx.fillStyle = "#fff";
ctx.fillRect(0,0,WIDTH,HEIGHT);
ctx.save();
ctx.fillStyle = "#000"
ball.draw();
player.draw();
ai.draw();
ctx.restore();
}