I'm building an app using the HTML 5 canvas tag, and so far it works great. The next step for the app is layering, so that I have a background image and them some more layers on top of that. Right now I'm doing this:
this.drawMarkers = function(markers) {
selectedImage = null;
map.width = map.width;
for(var i in markers) {
for(var j in markers[i]) {
var holder = markers[i][j];
if(holder !== null) {
var marker = new Marker(new Cell(holder.row, holder.column), holder.type);
marker.src = holder.src;
marker.draw();
}
}
}
initGrid();
}
where i is the layer and j is the things on that layer. The initial code for this just drew everything on one layer and it worked great, and this one does too if everything downloads in the correct order. If not, things get stacked incorrectly. For example, if the background is large and ends up downloading last, it ends up clearing out everything on top of it due to everything being asynchronous.
Are there any frameworks out there for adding layering to canvas to avoid this? I'm fine if things don't load in the correct order, so long as the stacking is preserved.
First question to ask yourself:
If you draw it repeatedly, say every 100 milliseconds, does the problem go away?
If no, the "drawing in the wrong order" is something you are doing and not an async error. I suspect this might be the case.
Images may not draw, but nothing should draw in the wrong order, assuming your draw routine is correct and that you are redrawing everything each time. According to the canvas spec, a call to drawImage does nothing if the image isn't loaded, so incorrect stacking should be impossible unless it is the order of your own calls.
In any case, you should wait until all of the images you are going to use have loaded before you draw. Use their onload events and/or $(window).load() or jQuery(document).ready, etc.
Some other notes:
Depending on how interactive your canvas is and how the layers work, sometimes it is better to use multiple canvases stacked on top of each other to increase performance, especially if a top layer changes very little and a layer underneath changes often.
If your background is a static file such as a png, set the CSS background-image of the canvas to that image to make the drawing easier on yourself.
Related
I am trying to make a drawing program with HTML canvas. You can see a version of it here: https://naclcaleb.github.io/Draw
My architecture works somewhat like this:
I have a master Canvas object, which contains a list of layers that it draws.
Each layer has a list of "Strokes". Each stroke contains a list of actions required to draw it.
I've had a few problems with lag. First, it was taking too long to draw all the strokes in the list.
I solved this by "rendering" the layer every time a stroke is added, so it's just an image.
That worked fine, except that I still had to draw every stroke when the user pressed Ctrl+Z, so I could reset the render.
So every time they tried to undo, it would take a while to compute - usable, but not ideal.
To fix this, I tried the same approach with each stroke - when it's done being created, "render" it by drawing it on a different hidden canvas, then turning it into an image with a DataURL.
(NOTE: I did try using getImageData and putImageData instead, but then it would replace the previous strokes with empty pixels)
And now I'm getting a pretty big problem:
Whenever I make a stroke, it immediately disappears. But once I press Ctrl+Z, all the previous strokes suddenly become visible.
I've been trying to figure this out for a while now.
Here's basically what happens:
The mouse is released, which triggers this event listener (found in the layer class):
this.el.addEventListener("mouseup", function(e){
//You can ignore this line...
if (active){
//...and this if statement. It just handles the erasers, and has been tested to work
if (that.erasing){
CURRENT_TOOL.currentStroke.actions.push({
func: ctx.setGlobalCompositeOperation,
params: ["source-over"]
});
}
//Have the current stroke create its render - I have used console.log to ensure that this does get run, and that the dataURL produced shows the correct image.
CURRENT_TOOL.currentStroke.createRender();
//The endStroke method returns the currentStroke, which I grab here
var newStroke = CURRENT_TOOL.endStroke(ctx);
//"that" is just a reference to the layer instance
//Add it to the strokes list
that.strokes.push(newStroke);
//Update the render
that.updateRender();
}
});
As you saw in the event listener, it calls an updateRender method. Here is that method (also in the layer class):
//Clear the canvas
this.ctx.clearRect(0, 0, this.el.width, this.el.height);
//Put the LAYER's render onto the screen
this.ctx.putImageData(this.render, 0, 0);
//Draw the stroke - I'll give you the draw function, but I've confirmed it does get run
this.strokes[this.strokes.length-1].draw();
//Now get the image data and update the render
this.render = this.ctx.getImageData(0,0, this.el.width, this.el.height);
As you saw in that function, it calls the stroke's draw function (found in the stroke class):
draw(){
//Ignore this if
if (this.render === undefined){
for (var i = 0;i<this.actions.length;i++){
this.actions[i].func.apply(this.ctx, this.actions[i].params);
}
}
else {
//I've used console.log to confirm that this runs.
//Draw the render
this.ctx.drawImage(this.render, 0, 0);
//I've also console.logged the dataurl of the layer before and after drawing the image, and neither have the render drawn on them.
}
}
This seems to say that the this.ctx.drawImage is not being run? Or that it's being run in the wrong way?
Anyways, that's that, and though I apologize for the longevity of this question, I'm hoping someone can help me. This is really my last resort.
One more thing: I got it to work in the link I gave earlier by rendering the layer using the same method I render the strokes - using an image with a dataURL instead of getImageData and putImageData. I'm not really sure what to think about that...
I would like to create an element, that shows a red circle. Once the user clicks on it, she can record her voice. In order to show the LIVE mode, I'd like to make the circle "breath" according to the incoming frequencies.
I'm experimenting with a <canvas> element. That means it creates a circle that gets bigger and smaller, depending on the variable arcrad. However, the lines are being drawn correctly, but they do not disappear afterwards. I tried to apply .clip() but can't get it to work...
if (arcrad <= 10) arcrad = 10;
analyserContext.beginPath();
analyserContext.arc(100,120,arcrad,0,2*Math.PI);
analyserContext.closePath();
analyserContext.lineWidth = 2;
analyserContext.strokeStyle = 'red';
analyserContext.stroke();
Any ideas - or completely different strategies for this use case?
Canvas will overdraw by default. For your animation you’ll need to clean the canvas at the start of each frame. Use something the following at the start of your drawing function:
analyserContext.clearRect(0,0,200,200);
assuming your canvas is 200 pixels wide and high. It’s worth pointing out that sometimes you don’t want to completely clear the animation field every frame. For example, if you were to draw a semi transparent rectangle over the frame at the beginning (instead of clearing it) then you’d end up with a basic ‘bullet time’ style effect.
It's a normal behavior. Once something it's drawn on the canvas, it's there forever. You have to think like if you were painting something: what has been done cannot be undone.
Luckily, you still have solutions:
1) redraw another circle on top of the first one with the background color. It's really not the recommend way, but it still can be useful
2) use clearRect method (see How to clear the canvas for redrawing)
There are numerous ways to clear a canvas pre drawing to create animation:
How to clear the canvas for redrawing
simplest in my mind:
canvas.width=canvas.width;
though can equally use clearRect (which is actually quicker and won't reset the entire canvas if that is an issue regarding transforms etc!) over the region or whole canvas.
Get the likes of:
http://jsfiddle.net/dw17jxee/
When animating one layer in canvas the graphics become choppy if there are other layers present, see fiddle (click RUN to see animation): http://jsfiddle.net/Q97Wn/
If i change this line:
opts.percentageIndicator.clearRect(0, 0, opts.percentageIndicator.width, opts.percentageIndicator.height);
To:
opts.percentageIndicator.clearRect(0, 0, opts.canvas.width, opts.canvas.height);
Then everything goes smoothly, except that this will remove the other layer completely.
I could solve this issue by having both in one canvas each, but i was hoping for structure-purposes that i could avoid that. Any suggestions?
First of all, canvas.getContext() not generating new context, it returning already existing instance, so the lines:
opts.centerCircle = opts.canvas.getContext('2d');
// ...
opts.percentageIndicator = opts.canvas.getContext('2d');
Would mean the same thing.
So I advice you to do like this:
http://jsfiddle.net/Volter9/Q97Wn/2/
What I did I just changed both contexts to one property and after rendering base added:
opts.ctx.strokeStyle = opts.indicatorColor;
Good luck!
I've created a jQuery plugin based on somebody else's Chrome experiment that inserts a canvas element into your target element, and draws an interactive starfield in the canvas.
Each time you resize the window, the canvas element is removed and then restored so that its size matches its parent element and everything animates properly; it's responsive.
However, whenever it's restored, the speed of the animation increases. Why does it do this? I thought all the variables (including speed) were reset to their defaults with the this.start() method.
You can see the code (and demo) on CodePen; you can also fork it on Github, though I think the Github version is several commits behind my own.
(Also, this is my first real jQuery plugin, so if you see any issues, by all means, let me know.)
Any clues?
Using cancelAnimationFrame alone won't necessary stop the animation loop (it turns out).
To be absolute sure you will need to use a conditional check as well - a generic example:
var isPlaying; /// our condition
function loop() {
/* funky stuff here */
If (isPlaying === true)
requestId = requestAnimationFrame(loop);
}
Then starting it:
functiom start() {
isPlaying = true;
loop();
}
Now when you want to stop the animation you need to do:
function stop() {
isPlaying = false;
/// kill any request in progress
if (requestId) cancelAnimationFrame(requestId);
}
If you don't do this you risk stacking calls to the loop - for example:
If you resize and cAF isn't stopping rAF from re-trigger the loop, the old loop will still run in the background and you will start a new loop on top of that.
That is why you see speed increases as the old and the new loop will both increment the positions before the stars are drawn to screen.
On the third re-size yet another loop is started, and eventually the whole thing will block the browser.
However
Instead of utilizing start and stop of the loop I would recommend you the following approach:
Create canvas once
Start the loop only once
In this case, a re-factoring of the whole re-size mechanism could be beneficial (for instance, separate needed initializations (width and height of element) from first time initializations that can be re-used later).
There is no need to re-init the stars for each re-size as you will use the width and height to check their boundaries (canvas will do the clipping).
When resizing you can consider using a conditional flags to prevent render while re-sizing.
Although generally, a condition to block rendering while canvas changes size is really not necessary due to the single-thread nature of JavaScript and in cases such as this you do boundary check on current element size. Canvas itself will take care of the clipping for you.
And that being said: there should be no need to re-create the canvas element each time.
This creates an unnecessary overhead. Simple set new width and height on its properties if canvas is already created:
if (typeof canvas === 'undefined')
canvas = /* only create if it doesn't exist */
canvas.width = width;
canvas.height = height;
PS: I "hampered" a version with some brute-force-ish implementations of the above. It's far from complete or of high quality but takes out some of the pain for the sake of example to give you a couple of pointers.
Please adopt to suit your needs.
Update:
To include more info from the additional comments:
when a new size is set on a canvas element its context is reset to default (fillStyle becomes transparent, strokeStyle black, transform is reset and so on).
This means that all non-default settings must be set again after each new size is set.
Setting a new size may (and typically do) clear the content of the canvas as well so that all pixels becomes transparent black.
For anybody struggling with manually updating a canvas element's dimensions:
Resizing the canvas element results in it discarding anything that's been drawn to it up to the point of the resize.
This script's animation should have continued to draw to the canvas after resize, but the only thing that would update was the fillRect of the background; the stars disappeared.
The only way to get the stars back after changing the dimensions of the canvas element: an extra call to context.strokeStyle. I have no idea why; if anybody could shed some light on the matter, I'd be much obliged.
Edit: As per comments below, everything in the canvas resets—including stroke and fill style (both default to black, apparently). So as the resize fires, I had to re-define stroke and fill styles.
I'm writing a game using HTML5/WinJS on Windows 8. I'm trying to produce the effect of a bullet or missile firing at something; however, I can't seem to get the object to go through another image in the background without trailing a border. My working theory is that the border I'm seeing is caused by using clearRect. Here's my code:
var moveBullet = function(missile) {
if (missile.Image == undefined || missile.Image == null) {
var image = new Image();
image.src = "/images/missileImg.png";
image.onload = function () {
canvasContext.clearRect(missile.PointX - (image.width / 2), missile.PointY, image.width, image.height);
missile.PointY += BULLET_SPEED;
canvasContext.drawImage(image, missile.PointX - (image.width / 2), missile.PointY);
};
} else {
canvasContext.clearRect(missile.PointX - (missile.Image.width / 2), missile.PointY, missile.Image.width, missile.Image.height);
missile.PointY += BULLET_SPEED;
canvasContext.drawImage(missile.Image, missile.PointX - (missile.Image.width / 2), missile.PointY);
}
}
Is there a way to achieve this effect without using clearRect, or a more efficient way of restoring the background as it moves past?
Make your clearRect area a few pixels larger than the missile image. Drawing on a canvas in general has some built-in anti-aliasing. This means that if you draw a line with one color, then draw the same line with the background color, you'll not remove the original line. Something similar might be happening here, in which case a few extra pixels should help.
That said, there's a caveat to be aware of here.
First, I assume the background is separate element from the canvas? It looks like it as you're not redrawing that part on the canvas itself.
The reason I ask is that making repeated calls to clearRect on the same canvas will eventually show performance problems. What happens is that every call to clearRect accumulates into a complex region within the canvas--essentially its transparency mask. So every time the canvas has to be rendered, which happens any time you change it, it has to process that transparent area. Gradually, as you leave more and more small clearRect trails across the canvas, this region will become more and more complex and performance will drop.
I did this experiment with the Blizzard demo on the IE Test Drive site once, where I wondered why the demo was clearing the entire canvas with every animation frame. So I tried just clearing the trail behind each snowflake (and made each one a little bigger as I suggest above, because I had trails). Seemed like the right thing to do, but the performance plummeted by several orders of magnitude. Asking around within the IE team, they confirmed the region behavior I describe.
So the best thing to do, actually, is to do a clearRect on the entire canvas with every frame, then redraw the missile and any other bits that you're animating. This may seem counter intuitive, but ends up working best and avoids all these glitches with pixel trails.