I thought I'd have a little play around with Canvas animations, as I've not really done much with it before. So I forked this repo
Live demo
and decided my first stop would be to replace the 'red rect' with an image. Should be pretty straight forward I thought. So I'll just replace the ctx.rect(this.x, this.y, this.width, this.height); with a new Image()
For some reason, the image never appears.
Here is my Bird.draw method.
Bird.prototype.draw = function(ctx) {
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.fillStyle = this.fill;
ctx.fill();
base_image = new Image();
base_image.src = 'images/icon.jpg';
base_image.onload = function(){
console.log('loaded');
ctx.drawImage(base_image, this.x, this.y, this.width, this.height);
return ctx.fillText("" + this.score + "/" + this.highest, this.x, this.y - 2);
}
};
and a full working JSFiddle here
I've also tried swapping out he image.load for a pattern, but it still didn't load.
The only difference I find between your code and working code on a project I have is that I assign the url to image's src after defining image onload listener.
this.canvas.loadImage = function (image, x, y, w, h) {
var context = this.getContext("2d");
var myImage = new Image();
myImage.onload = function () { context.drawImage(myImage, x, y, w, h); };
myImage.src = image; //<-- only actual difference I see with your code
return myImage;
}
This works for me
Problem
this inside an image.onload refers to the image (not Bird) so your this.x & this.y are undefined.
Solution
Attach a reference to your Bird to your base_image:
base_image.Bird=this; // this is your Bird
So here some refactored code (not tested--some tweaking may be required!):
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.fillStyle = this.fill;
ctx.fill();
base_image = new Image();
base_image.Bird=this;
base_image.src = 'http://upload.wikimedia.org/wikipedia/commons/f/f6/Choice_toxicity_icon.png';
base_image.onload = function(){
ctx.drawImage(base_image,
this.Bird.x, this.Bird.y, this.Bird.width, this.Bird.height);
ctx.fillText("" + this.Bird.score + "/" + this.Bird.highest,
this.Bird.x, this.Bird.y - 2);
}
Second Problem
The draw function is being called repeatedly. Therefore you're loading the image repeatedly and causing these bad effects depending on how fast the browser can load each new smileyface:
In IE11 the smileyface remains on the page
In FF27 the smileyface initially appears on the page but disappears on mouse movement
In Chrome32 the smileyface is loaded and drawn but disappears immediately
Second Solution
Pull the image loading out of draw() and just do drawImage(mySavedSmileyFace) inside draw().
Related
I'm trying to create a JavaScript object that has a method which allows a rectangle to rotate around its own origin during a rAF callback.
Things I have done:
Calculating the origin of an object within the canvas space.
Using ctx.save() and ctx.restore() - this is where my issues arise.
When I use the save() and restore() methods to push and pop the saved states within method calls for different objects it either doesn't change anything, or stops the entire animation.
The rotation in my example appears to be applied globally to the canvas (which is how the functionality is specified on MDN). I'm trying to translate around origin around multiple instances. I've spent hours on this.
Is there something going on with the inheritance mechanism in JavaScript that's not resetting my transforms for different instances of the rectangle objects in the code example?
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
// draws a rectangle at given coordinates
this.draw = function() {
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
ctx.restore();
}
// rotates the rectangle around it's center relative to a given xy position
this.rotateRect = function() {
ctx.save();
ctx.translate(this.x + this.w * 0.5, this.y + this.h * 0.5);
ctx.rotate(this.radians);
ctx.translate(-this.x -this.w * 0.5, -this.y - this.h * 0.5);
//ctx.restore()
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
redRectangle.rotateRect();
}
mainAnimationLoop();
I have tried rotating multiple rectangles around their own origin at different positions without animation using save() and restore() - which worked.
Additionally, I have tried moving the rotate method inside of the draw method and the results were the same. My rationale was that the rotation would be applied as a function call within draw() - the rationale was clearly wrong.
Any insight towards a solution would be greatly helpful. I have included a link to the pen on codepen to see the concept in motion.
Instead of drawing the rects at (this.x, this.y) you may draw them at 0,0 and translate them to (this.x, this.y);
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
this.rotation = 0;
// draws a rectangle at given coordinates
this.draw = function() {
this.rotation += this.radians;
ctx.save();
ctx.fillStyle = this.color;
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillRect(0,0, this.w, this.h);
ctx.restore();
}
this.update = function() {
// animation updates
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
}
mainAnimationLoop();
<canvas></canvas>
I am new to canvas and java script and have no idea why it is not working. The 2 alerts are firing but color of the canvas is not changing every 1 sec as I intended it to do.
var canvas = function(width, height, color){
this.width = width;
this.height = height;
this.color = color;
var instance = document.createElement('canvas');
document.body.appendChild(instance);
instance.width = this.width;
instance.height = this.height;
var ctx = instance.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0, 0, this.width, this.height);
this.changeBackground = function(color){
ctx.fillStyle = color;
ctx.fillRect(0, 0, this.width, this.height);
}
this.clear = function(){
ctx.clearRect(0, 0, this.width, this.height);
}
var flag = true;
setInterval(function(){
if(flag) {
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, this.width, this.height);
alert('blue');
}
else {
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, this.width, this.height);
alert('green');
}
flag = !flag;
},1000);
}
var gameCanvas = new canvas(800, 600, 'green');
The issue is this inside of setInterval is the global window object, so this.width is undefined. The easiest way to fix this is to convert the function passed to setInterval into an arrow function. Here is a fiddle
setInterval(() => {
...
},1000);
Arrow functions inherit their context, so this is still your canvas object.
Note: arrow functions are not supported in IE v11 and below. Here is the list of supported browsers.
If you can't use arrow functions, you can get the size of the canvas from the ctx with ctx.canvas.clientWidth and ctx.canvas.clientHeight Here is a fiddle
Writing setInterval( () => { instead of setInterval(function(){ could solve this issue.
Fat-arrow function or lamda function or () => will point this to your class-function instead of window variable.
Using function() inside a callback will always point this to the object that the implementation of used function came from.
In your case setInterval is a function of window global variable
if(flag) {
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
alert('blue');
}
else {
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.width, canvas.height);
alert('green');
}
this inside setInterval contexts to window. Fixing this would fix your issue.
I already create my canvas on my page, however when I try to import an image on the canvas, it does not appear, here are the code I used. (the makeGameArea is a method I created to make a canvas on the webpage, the code of this method is not shown because it is so long to put it here)
Here are the following code that should have problem
var myGameArea2 = makeGameArea(700, 150, "myArea", "white");
var myContext2 = myGameArea2.getContext("2d");
var myImage = new drawImage(myContext2, "sonic.gif", 91, 97, 0, 0);
myImage.draw();
function drawImage(ctx, src, width, height, x, y){
this.image = new Image();
this.image.src = src;
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.draw = function(){
this.image.onload = function(){
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
}
}
}
Is there anything wrong with my drawImage() method?
HTMLImage.onload will fire only once per src setting.
Here you are attaching your handler in the draw method, which I guess will be called later on. At this moment, the event will already have fired, and hence will never be called, since it won't fire again unless you reset its src.
Fixed code block:
function drawImage(ctx, src, width, height, x, y){
this.image = new Image();
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.draw = function(){
ctx.drawImage(this.image, this.x, this.y, this.width, this.height));
};
// if you want it to draw as soon as possible
this.image.onload = this.draw.bind(this);
this.image.src = src;
}
This is my script code..
<body onload= "startGame()">
<script>
var Earth;
var Mercury;
var Venus;
function startGame() {
Earth = new component(152, 183, 'earth.png', 800, 75);
Mercury = new component(122,151, 'mercury.png', 300, 400);
Venus = new component(152, 183, 'venus.png', 520, 240);
GameArea.start();
}
var GameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 1000;
this.canvas.height = 642;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
//make game pieces
function component(width, height, color, x, y) {
this.type = "image";
this.image = new Image();
this.image.src = color;
this.width = width; this.height = height;
this.x = x; this.y = y;
this.update = function() {
ctx = GameArea.context;
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
}
}
function updateGameArea() {
GameArea.clear();
Earth.update();
Mercury.update();
Venus.update();
}
</script>
</body>
The code is when browser open, startGame() function is start.
I draw canvas, and show planets on the canvas.
Please refer to the image.
This is when javascript on running image.
Their has a Earth, Mercury, Venus.
That planet is all object....If i thinks...
I want to click event when users click on "Earth" object..
But I don't know how to do that....
What should I refer to?
Please advice to me..! Thanks.
You can't, really. When you're drawing on a canvas you're essentially drawing one big bitmap image. The shapes you draw get added to it and that's it. They're not actually objects.
You have two options:
Catch the click event from the canvas element, get the mouse coordinates and use that to infer which object was clicked.
Use a library like EaselJS. It's sort of an API around the canvas that makes working with it much easier. It will allow you to attach click handlers to the objects on the canvas.
You're basically going to have to track where your Planets are on the canvas, then set up an event listener on the canvas itself. From there you can take the coordinates of the click event and go through all your Planets to test.
HTML:
<form id="myCanvas" ... >
</form>
Get canvas element:
var Earth = document.getElementById('myCanvas');
To add a click event to your canvas element, use...
Earth.addEventListener('click', function() { }, false);
Check this example with informations about #Brother Woodrow said
I am having trouble displaying pictures on my canvas, especially when using Chrome.
I have a method that is called to draw a portrait (name, img, borders), but context.drawImage() doesn't seem to be working in Chrome. Any solutions?
Portrait.prototype.draw = function (context, x, y, tX, tY) {
context.save();
context.translate( tX, tY );
context.fillStyle = 'blue';
context.strokeStyle = 'black';
context.strokeRect(x, y, 160, 200);
context.fillRect(x, y, 160, 200);
context.stroke();
// adding the image
context.beginPath();
var img = new Image();
var link = this.getImageUrl();
img.src = link;
//context.drawImage(img,x+5,y+5); //works mozzila
img.onload = function () { context.drawImage(img,x+5,y+5); }
// partial work chrome but on refresh displays e.g two different portrait images in turn in a random portrait (if multiple portraits on canvas)
// text box
context.beginPath();
context.fillStyle = '#ffffff';
context.fillRect(x+5,y + 165,150,30);
// text
context.beginPath();
context.fillStyle = 'black';
var n = this.getName();
context.font = "25px Aerial";
context.fillText(n,x+5,y+190); // should give the name
context.restore();
};
You're passing img.onload a function which will be executed asynchronously, meaning the other lines of code will proceed before it's done. Wrap the entire "draw" in your image.onload function.
Portrait.prototype.draw = function (context, x, y, tX, tY) {
var img = new Image();
var link = this.getImageUrl();
img.src = link;
img.onload = function () {
context.save();
context.translate( tX, tY );
context.fillStyle = 'blue';
context.strokeStyle = 'black';
context.strokeRect(x, y, 160, 200);
context.fillRect(x, y, 160, 200);
context.stroke();
context.drawImage(img,x+5,y+5);
//...
}
};