how to force a display update in canvas - html

if I draw to the canvas a lot in quick succession, e.g. a context.fillRect in a loop, browsers seem to wait until the loop has finished before any of the drawing is displayed (possibly via double-buffering)
Is there any way to force the browser to update the display, either explicitly or implicitly after each draw operation?

It is not really because of any double-buffering that you don't see the results, but rather because JavaScript in the web browser is single-threaded. If you similarly create a single loop in JavaScript that repeatedly does something like myDiv.style.top = parseInt(myDiv.style.top) + 1 +"px"; you will see that nothing will visibly change in the browser—even over many seconds—until your JavaScript has finished executing.
To draw changes and see the results on the screen, you need to use setInterval or setTimeout to yield control back to the browser but ask to run code at some point in the future.
For example, to draw a new random, randomly-colored rectangle on the canvas 15 times a second:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
setInterval(function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
var r=Math.random()*255, g=Math.random()*255, b=Math.random()*255;
ctx.fillStyle = 'rgb('+r+','+g+','+b+')';
var w=Math.random()*canvas.width, h=Math.random()*canvas.height;
var x=Math.random()*(canvas.width-w), y=Math.random()*(canvas.height-h);
ctx.fillRect(x,y,w,h);
},1000/15);

It is better to use window.requestAnimationFrame() for better browser behaviour.

Related

How to cache a whole layer right before dragstart and revert it back on dragend?

I'm currently trying to speed my web app for mobile devices a little bit up, but now I'm stuck at the most important part - caching. How is it possible to cache a entire layer right before the user starts to drag it and revert it back to usable Kinetic.Nodes when the drag-action has stopped?
At the moment I start caching on
stage.on('mousedown touchstart', function(){ // CACHING})
but the problem here is, that the user has to perform a second mousedown touchstart event to "grab" the cached image, which, of course, starts a new caching.
In this case my questions would be:
How can I pass the mousedown touchstart event to the cached image,
so the user can drag it with one fluent movement?
How can I speed up caching? (It takes 1-2 seconds for the cached image to appear. Is it useful to cache it in a setInterval after every, lets say 4 secs, and use this precached image or causes that a too high performance drain?)
I highly appreciate any kind of suggestions regarding my problem or further tips&tricks to speed up things.
Based on this statement:
stage.on('mousedown touchstart', function(){ // CACHING})
I'm assuming that on mousedown touchstart you call layer.toImage() or stage.toImage() and you want to drag the new image on that one click/tap.
You can invoke the drag event on the new generated image by using the .startDrag() function: Kinetic.Shape#startDrag
You can then invoke .stopDrag() on mouseup touchend to stop the drag. Kinetic.Shape#stopDrag
Something like:
var image, ox, oy;
stage.on('mousedown touchstart', function(){
// CACHING
stage.toImage({
width: stage.getWidth(),
height: stage.getHeight(),
callback: function(img) {
var image = new Kinetic.Image({
image: img,
draggable: true
});
ox = image.getX();
oy = image.getY();
image.startDrag();
}
});
});
stage.on('mouseup touchend', function(){
image.stopDrag();
//Calculate dx, dy to update nodes.
var newX = image.getX();
var newY = image.getY();
var dx = newX-ox;
var dy = newY-oy;
var children = layer.getChildren();
for (var i=0; i<children.length; i++) {
children.setX(children.getX()+dx);
children.setY(children.getY()+dy);
}
image.hide(); //or remove() or destroy()
layer.draw();
});
Note you need to update your original nodes after dragging the cached layer.
Another Note I haven't tested the code but I believe you could do something along the lines of what I've got up there.
Small UPDATE: You also should probably hide() the original layer while dragging the cached layer image! :) And then show() it again when you hide the cached image layer.
Honestly I'm not sure how you would speed up that cache time unless you can predict when the user needs to click/tap the stage to move. I think your suggestion would cost more performance than it would save.
I'm guessing that the desktop caches the image faster than on your mobile? It might fall into just being a limitation of KineticJS performance on Mobile vs Desktop...
UPDATE
Okay, I have an idea for #2, it's not exactly a fix but it might work better for you.
Separate your stage mousedown event with your touchstart event. mousedown will be the same but touchstart we want to handle differently.
On stage touchstart we want to drag the entire stage like normal, but meanwhile run the code for the original mousedown to cache the layer.
When the cached image finishes loading (takes 1-2 seconds you say), use .stopDrag() on the original stage and hide it. At this moment you want to store the current x and y values of the stage, so that you can still calculate dx,dy. Then immediately call .startDrag() on the new cached image and continue on like we did for mousedown.
How to know when the cached image finishes loading? I think that's what the toImage() callback function is for. If not, than hopefully a javascript onload event will work to determine when the image finishes generating.
The end result will be that you'll get your slow choppy drag on the stage for touch events UNTIL the image is cached. Then we flip the switch and stop dragging the stage, start dragging the cached image, and then revert/update the stage on touchend.
Hope that works as a semi-solution to your problem...
ANOTHER UPDATE
Okay here's another idea that actually might help your performance!
If your stage isn't modifying nodes very often, you can pre-cache the stage image so that it's already generated, and .hide() it. Then when you need to drag it, you just need to set the x,y of the cached image to match the stage's current x,y and then .show() the cached image. This will eliminate the time needed to wait/load the cached image when you first start dragging.
If you do happen to add a node or move a node or anything, after that cache the image. Hopefully this is manageable as we don't want to cache the image too often (drains performance). Again the cached image will be ready for your stage.drag event beforehand.
The goal is to have the stage cached before you do mousedown touchstart and start dragging. Hopefully this helps.

Reset canvas after error

I'm seeing the following behavior in a WebKit-based browser on embedded device: after there is an error drawing to the canvas (I'm not sure yet exactly about the nature of the error, but some times it looks like as if it failed to draw because the drawing was too slow...), the canvas itself becomes stale. I.e. no more drawing commands will affect the image (the error is caught in JavaScript code).
As right of now I'm not able to fix the error - how would I cause the canvas to refresh, as in recover after drawing error?
You can try to force re-initialization with something like this:
try {
//to catch the error you recevie
} catch(err) {
var tempHeight = canvas.height;
canvas.height = 0;
canvas.height = tempHeight;
}
(some would say just set canvas.height = canvas.height (or width) but this doesn't work in all browsers).
Another approach is to detach the canvas element itself, create a new element and attach that to the original parent with a attachChild.
However, these are hacks and hopefully the bug you see will be removed in next update.

Stop canvas flicker on re-render

I have written a jQuery plugin that will render four polygons onto a canvas and fill them with an image.
When you mouse over a polygon it is moved to a separate top layer and re-rendered so it expands and then contracts if you mouse out.
Is there any way to do this so that it doesn't flicker?
I've had a look a this question but don't quite understand what the accepted answer is doing (I know it draws the image on a seperate layer but I don't know how it checks if this has finished rendering before using it) or how to include it in my jsfiddle code
As it turns out it was because I was making a new image object everytime I made the drawing:
var imageObj = new Image();
imageObj.src = polyArray[4];
imageObj.onload = function () {
poly.setFillPatternImage(imageObj);
stage.draw();
}
I changed this so that these objects were created and cached into an array and then re-used. This enabled the re-rendering to run a lot faster and removed the flickering:
http://jsfiddle.net/peteng/KAkvX/10/

How to find HTML5 canvas element under cursor?

I know there is this question which suggests looping through all canvas elements on each mouse move.
However, is there a more effective way than looping through all the elements?
I have quite a few elements on the canvas and looping through them all on each mouse move could be very, very slow.
Yes, you have to loop through all of your elements.
That's how nearly all hit testing/object picking works.
Now there are a lot of optimizations you can do, like making sure that the mouse coordinate is in the rectangular bounds of the object before you test its real bounds (unless they're all rectangles, which would make everything convenient and fast, even if there are 10,000 objects). If you have a million objects, you could also split up their location into quadrants, or otherwise add a layer of isolation.
But at the end of the day you need to loop through every relevant object and ask if its bounds intersect the mouse point. There's no way of getting around that. It ought to be fast, and if you've implemented something that is very slow then you should post it so we can take a look and give you pointers.
If you need to see a live example I made a simple tutorial on picking and moving shapes in a canvas exactly for explaining situations like this.
Looping through 10000 of elements is still no effort in javascript.
However you might consider looping throught them with some interval not on every mousemove.
var mouseX, mouseY;
$(document).on("mousemove", function(e) {
mouseX = e.pageX;
mouseY = e.pageY;
/* dont do any probing there */
});
setInterval(function() {
checkCollision();
}, 25);

How to Copy Contents of One Canvas to Another Canvas Locally

I'd like to copy ALL contents of one canvas and transfer them to another all on the client-side. I would think that I would use the canvas.toDataURL() and context.drawImage() method to implement this but I am running into a few issues.
My solution would be to get Canvas.toDataURL() and store this in an Image object in Javascript, and then use the context.drawImage() method to place it back.
However, I believe the toDataURL method returns a 64 bit encoded tag with "data:image/png;base64," prepended to it. This does not seem to be a valid tag, (I could always use some RegEx to remove this), but is that 64 bit encoded string AFTER the "data:image/png;base64," substring a valid image? Can I say image.src=iVBORw...ASASDAS, and draw this back on the canvas?
I've looked at some related issues:
Display canvas image from one canvas to another canvas using base64
But the solutions don't appear to be correct.
Actually you don't have to create an image at all. drawImage() will accept a Canvas as well as an Image object.
//grab the context from your destination canvas
var destCtx = destinationCanvas.getContext('2d');
//call its drawImage() function passing it the source canvas directly
destCtx.drawImage(sourceCanvas, 0, 0);
Way faster than using an ImageData object or Image element.
Note that sourceCanvas can be a HTMLImageElement, HTMLVideoElement, or a HTMLCanvasElement. As mentioned by Dave in a comment below this answer, you cannot use a canvas drawing context as your source. If you have a canvas drawing context instead of the canvas element it was created from, there is a reference to the original canvas element on the context under context.canvas.
Here is a jsPerf to demonstrate why this is the only right way to clone a canvas: http://jsperf.com/copying-a-canvas-element
#robert-hurst has a cleaner approach.
However, this solution may also be used, in places when you actually want to have a copy of Data Url after copying. For example, when you are building a website that uses lots of image/canvas operations.
// select canvas elements
var sourceCanvas = document.getElementById("some-unique-id");
var destCanvas = document.getElementsByClassName("some-class-selector")[0];
//copy canvas by DataUrl
var sourceImageData = sourceCanvas.toDataURL("image/png");
var destCanvasContext = destCanvas.getContext('2d');
var destinationImage = new Image;
destinationImage.onload = function(){
destCanvasContext.drawImage(destinationImage,0,0);
};
destinationImage.src = sourceImageData;