I am trying to draw a kitten in an HTML5 canvas through a class constructor using TypeScript but I am confused on how to achieve the task. I have commented the code to show what I have attempted to do based on the behavior that I expected vs what actually works. Thank you very much for your timer and advice.
module Game {
export class Test {
width: number;
height: number;
cellWidth: number;
cellHeight: number;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
constructor() {
this.width = 28;
this.height = 31;
this.cellWidth = 20;
this.cellHeight = 20;
this.canvas = <HTMLCanvasElement> document.getElementById("game_canvas");
this.context = this.canvas.getContext("2d");
this.canvas.width = this.width * this.cellWidth;
this.canvas.height = this.height * this.cellHeight;
this.context.fillStyle = "blue";
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
/*
I was assuming that by accessing the this.context property
I would have direct access to the canvas and I will be able to use
drawImage to draw the kitten on it; however, that approach
produces no kitten in the canvas.
Only by using the .onload method it works.
I am using the () => notation so that the this inside the block
is referring to the class.
I have seen many JavasScript files in which images are simple drawn
through:
context.drawImage(image, 0, 0);
They are not embedded in .onload
I have tried to Google information but I cannot pinpoint what is
happening.
*/
}
}
}
As per my comments here is my answer: quite simply because you are declaring a new Image() and setting the src, your drawImage call will no doubt be in advance of the src being loaded... if you were to use a previously loaded image (e.g. from the DOM) then the creation of a new image and load would not be required
setting the src triggers the load - doing it in another class still subjects you to the wait time for load and you cannot be sure - I would say using onload is bulletproof and essential if you are the loader of the images you are using - the only alternative methods are when the images are already loaded into the DOM (or preloaded elsewhere), you may find the canvas examples you have seen are designed to be initiated onload of the images concerned
From your code:
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
Using onload is the defacto way you get an image that you can draw on a canvas. A microoptimization would be to keep a dictionary of loaded images so you get to reuse them if drawing multiple kittens.
Related
I've been attempting to program a start up section for my space invaders game. However, when the body loads one of my objects (the startbtn variable) is not appearing on the canvas.
Bellow is the code to my game
<html>
<head>
<title>Space Invaders</title>
</head>
<body onload="startGame()">
<script type="text/javascript">
var startbtn;
function startGame(){
startbtn = new compenent(50, 20, "blue", 120, 10);
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function(){
this.canvas.width = 420;
this.canvas.height = 220;
ctx = this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas,
document.body.childNodes[0]);
ctx.font="35px Verdena";
ctx.fillText("Welcome to Space Invaders",10,50);
},
clear : function(){
this.context.clearRect(0, 0, this.canvas.width ,
this.canvas.height);
}
}
function compenent(width, height, color, x, y){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function updateGame(){
myGameArea.clear();
startbtn.update();
}
</script>
</body>
</html>
If someone could help me, that would be great.
The main problem is that updateGame() is never called. You can add it to the startGame() function to run after the canvas is set up. Having done this, the blue box shows but not the text. The reason is because myGameArea.clear(); clears the text that was drawn in the myGameArea.start function. Removing myGameArea.clear(); results in:
var startbtn;
function startGame() {
startbtn = new compenent(50, 20, "blue", 120, 10);
myGameArea.start();
updateGame();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 420;
this.canvas.height = 220;
ctx = this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas,
document.body.childNodes[0]);
ctx.font = "35px Verdena";
ctx.fillText("Welcome to Space Invaders", 10, 50);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width,
this.canvas.height);
}
}
function compenent(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function updateGame() {
//myGameArea.clear();
startbtn.update();
}
startGame(); // moved from body.onload
However, at this point, it may be clear that there are some potential design obstacles ahead in making a full-featured space invaders game. The w3schools tutorial you're using is attempting a basic entity-component system, but this might be confusing for starters, so I'd encourage you to poke around at other tutorials for context if nothing else.
As your example is moving towards, animations have basic parts:
initialization routine (create objects and set initial positions for everything)
update routine (reposition objects, calculate collisions and damage)
rendering routine (draw the frame)
The init runs one time, or in some cases whenever an animation (or game level) needs resetting. The update and rendering routines run many times a second and together constitute the main animation loop (each one executes once per frame, so they're essentially parts of the same "animation loop" function):
----------------
| |
v |
init -----> update -----> render
This requires that each routine be responsible for their task and nothing else. Every call to render should clear the screen, then draw each and every visible object. The init function shouldn't have any rendering code, for example.
Also worth mentioning: everything being drawn is an object (or entity). W3schools' tutorial gets you started on this path, but how are you going to distinguish a button component from a spaceInvader component? You might just make them separate objects/classes entirely for starters.
The next level of abstraction is a state, each state with its own init --> update --> render functions:
---------------------------------------- ----------------------------------------
| menu state | | game state |
| | | |
| ---------------- | | ---------------- |
| | | | | | | |
| v | | | v | |
| init -----> update -----> render | | init -----> update -----> render |
---------------------------------------- ----------------------------------------
What does all of this mean for your example? You're attempting to set up a menu state with some text and a button to begin the game. Managing a bunch of game states can get pretty complex, so graphics libraries like Phaser offer built-in state management systems. You can design one if you wish or postpone the problem and use a variable or boolean to determine which state you're in. For starters, I recommend working in a single state, then adding more when necessary. In other words, consider simplifying or skipping the menu state in favor of the game state until you're ready to work with states.
Another potentially challenging aspect of doing animations is handling mouse events. How do you know when your button was clicked on? At the least, you'll need to use an event listener and some math. Libraries can do it for you.
Either way, getting your loop going in JS will require requestAnimationFrame or (less likely/recommended) setInterval. Check out some basic examples from the MDN web docs.
I hope this helps offer a path forward and highlights some areas you may want to look into before going too much further with your current approach.
I am using createjs as my framework. I've placed a Bitmap on the canvas and I created a function to try and remove it but I keep getting an error message in the console that image is not defined. This is what my code looks like:
// onload=init() called in html doc
function init(){
var canvas = new createjs.Stage("canvas");
// Add image to canvas
image = new createjs.Bitmap("image.png");
image.x = 200;
image.y = 180;
image.scaleX = 0.35;
image.scaleY = 0.35;
canvas.addChild(image);
image.addEventListener('click', pop);
canvas.update();
}
//remove image from canvas
function pop() {
console.log('pop');
canvas.removeChild(image);
}
When I click on it, I get the console message "pop" followed by the error I mentioned above. I've tried moving the function inside init but I appear to get the same problem.
Make image as global variable, so that it can be accessed by all the functions in your case function pop.
var image;// defining 'image' variable as global
function init(){
var canvas = new createjs.Stage("canvas");
// Add image to canvas
image = new createjs.Bitmap("image.png");
image.x = 200;
image.y = 180;
image.scaleX = 0.35;
image.scaleY = 0.35;
canvas.addChild(image);
image.addEventListener('click', pop);
canvas.update();
}
//remove image from canvas
function pop() {
console.log('pop');
canvas.removeChild(image);
}
This is a scope issue. You defined image inside your init function, so it is not accessible on the pop method.
There are two easy fixes. Either move the var image outside of the init function, or use the click target instead.
var image;
function init() {
init = new createjs.Bitmap();
// etc
}
// OR
function pop(event) {
stage.removeChild(event.target);
}
Scope is really important to understand when building JavaScript applications, so I suggest getting to know it a little better :)
Cheers,
I need some direction here as I'm not clear on what I'm doing wrong. All I'm trying to do is a load a bitmap in the center of the canvas but it's not showing up. My file path is correct and I don't see what I might have coded incorrectly, where am I going wrong?
var canvas, stage, centerX, centerY;
function init() {
'use strict';
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
centerX = canvas.width / 2;
centerY = canvas.height / 2;
var ship = new createjs.Bitmap("../images/millenium.png"),
shipCenterX = ship.width / 2,
shipCenterY = ship.height / 2;
ship.x = centerX;
ship.y = centerY;
ship.regX = shipCenterX;
ship.regY = shipCenterY;
stage.addChild(ship);
stage.update();
}
The way this library appears to handle drawing to the canvas is by calling stage.update() which they recommend attaching to their "tick" event (e.g. http://www.createjs.com/docs/easeljs/classes/Stage.html)
Basically, we need to keep continually redrawing the canvas, and createjs gives us a method to do that, like so:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
stage.update();
}
However, since you haven't made your stage globally accessible, I tweaked your init function slightly so that we can access stage by returning it at the end of the function. Thus you can set stage in the global scope to the result of the function:
var stage = init();
And handleTick will use that stage by default. However if you're thinking of reusing your objects outside of your init() function, you may want to consider passing them to the init function or keeping their initial data structure outside of the init function to make them easier to access.
https://jsfiddle.net/jpgw1oka/
And make sure you are loading the CreateJS library: https://code.createjs.com/createjs-2015.11.26.min.js.
Remove the 'use strict'.
And make sure you are declaring your variables and function calls inside a window.onload event listener.
E.g.
var canvas, stage;
function init() {
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
var ship = new createjs.Bitmap("../images/millenium.png");
ship.x = Math.floor(stage.canvas.width * 0.5);
ship.y = Math.florr(stage.canvas.height * 0.5);
ship.regX = Math.floor(ship.image.width * 0.5);
ship.regY = Math.floor(ship.image.height * 0.5);
stage.addChild(ship);
stage.update();
}
I am trying to use InfoVis / JIT to render a force directed graph visualizing a network.
I am a newbie to both java script and JIT.
I have created my own custom node types using following code in my js file, which lets me display my image on the node.
$jit.ForceDirected.Plot.NodeTypes.implement({
'icon1': {
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.drawImage(img, pos.x, pos.y);
};
},
'contains': function(node,pos){
var npos = node.pos.getc(true);
dim = node.getData('dim');
return this.nodeHelper.circle.contains(npos, pos, dim);
//return this.nodeHelper.square.contains(npos, pos, dim);
}
}
I am assigning this custom node type to the node using "$type": "icon1" in the json data object. I do get image on the node, but the problem is that I am not able to hide it when required. I am able to hide the in-built node types like circle,square etc. using following code.
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
But the same code does not work for custom nodes.
Hence I tried to temporarily change the type of node to the built-in "circle" type, hid it and then re-setted the type of node to its original i.e. my custom node, icon1.
function hideNode( ){
var typeOfNode = node.getData('type');
node.setData( 'type','circle');
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
node.setData('type',typeOfNode );
}
I think this should work but the custom image comes back in a while on the canvas.
If I don't reset the type of node to its original i.e. in the above code and comment out the following statement and call hide function, then the node gets hidden.
node.setData('type',typeOfNode );
I am not able to figure out how by only setting a node's type to some custom type, the node is being rendered. Any help with this question will be appreciated.
I need to re-set the node's type to its original because I want the node to be restored when required by calling unhide function. If I don't reset node's type to the original then it would be rendered as a circle when restored.
I have gone through the API and the google group for JIT but couldn't find an answer.
Can anyone help?
Here's a look at a snippet from the Plot's plotNode function:
var alpha = node.getData('alpha'),
ctx = canvas.getCtx();
ctx.save();
ctx.globalAlpha = alpha;
// snip
this.nodeTypes[f].render.call(this, node, canvas, animating);
ctx.restore();
As you can see, the node's alpha value is applied to the canvas immediately before the node's render function is called. After rendering the node, the canvas is restored to the previous state.
The issue here is that your custom node's render function does not render the node synchronously, and the canvas state is getting restored prior to the call to drawImage. So, you can do one of two things:
1) Preload and cache your image (preferred approach, as this will also prevent image flickering and help with performance):
// preload image
var magnifyImg = new Image();
magnifyImg.src = 'magnify.png';
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var pos = node.pos.getc(true);
ctx.drawImage(magnifyImg, pos.x, pos.y);
}
or 2) save the canvas state, reapply the alpha, and then restore the canvas state after drawing the image in your onload handler:
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.save(); // save current canvas state
ctx.globalAlpha = node.getData('alpha'); // apply node alpha
ctx.drawImage(img, pos.x, pos.y); // draw image
ctx.restore(); // revert to previous canvas state
};
}
I'd like to have an HTML canvas context that I can paint to and read off-screen (in this example, writing text and reading the shape that is created, but it's a general question). I may also want to use a canvas as an off-screen frame-buffer.
I suppose I could create a hidden DOM element but I'd rather create it from JavaScript (I may want to create and destroy a number of canvas at runtime).
Possible?
You can create a new canvas element with document.createElement:
var canvas = document.createElement('canvas');
and then get the context from it. Just make sure you set the width and height. You don't have to add the canvas to the tree in order to make it work:
DEMO
But you definitely have to create that node. You could create a function for that though:
function createContext(width, height) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas.getContext("2d");
}
But that is where my competency ends... whether you can somehow transfer a context to another context or canvas, I don't know...
Its old but what about saving one canvas with toDataURL and copying to the other with drawImage. you could also use save and restore to make a frame buffer
function createCanvas(width, height) {
var c = document.createElement('canvas');
c.setAttribute('width', width);
c.setAttribute('height', height);
return c;
}
function canvasImg(canvas) {
var ctx = canvas.getContext('2d');
ctx.fillRect(0,0,canvas.width, canvas.height);
var img = canvas.toDataURL('image/png');
return img;
}
function placeImage(canvas, img) {
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0,0);
}
window.onload = function(){
var canvas = createCanvas(400, 400);
var hiddenCanvas = createCanvas(400,400);
var i = canvasImg(hiddenCanvas);
var img = new Image();
img.src = i;
placeImage(canvas, img);
document.body.appendChild(canvas);
}
There is apparently a new thing called OffscreenCanvas that was deliberately designed for this use case. An additional bonus is that it also works in Web Workers.
You can read the specifications here: https://html.spec.whatwg.org/multipage/canvas.html#the-offscreencanvas-interface
And see examples here: https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
Currently it is only fully supported by Chrome and is available behind flags in Firefox and Opera, but you can always check for the latest information on supported browsers here: https://caniuse.com/#feat=offscreencanvas
ps.: Google also has a dedicated guide explaining it's use in Web Workers: https://developers.google.com/web/updates/2018/08/offscreen-canvas
Both the CanvasRenderingContext2D and WebGLRenderingContext classes have the canvas element associated with them as the property canvas; and, like normal, both context instances and their canvases will be garbage collected when your code no longer makes references to them at run time.
You can use this function to create a new context
function newContext({width, height}, contextType = '2d') {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas.getContext(contextType);
}
const ctx = newContext({width: 100, height: 100});
console.log(ctx.canvas.width == 100) // true
And by making use of dereferencing you can easily create a clone of a DOM canvas for frame buffering like this:
const domCanvas = document.getElementById('myCanvas');
const frameBuffer = newContext(domCanvas);
frameBuffer.drawImage(domCanvas, 0, 0);
Which will create a context with the same width and height as the canvas element passed in. You can extend the function as needed.