Simple Html5 Canvas operation slows down browser - javascript

I've been playind with canvas lately and started today to work on using setInterval to refresh / animate it regularly.
I was surprised to see how this is heavy for the cpu and slows down eveyrthing. Looking at example online I m sure there is something wrong with my way of doing. I then simplified what I wanted to do at the maximum (not playing with image but rectangles, not using too many objects, etc) but still got the same problem.
I was trying to get a white flash (at 12fps) on top of two rectangles...So nothing complicate at all...
Here is my code.
function Canvas(name){
this.ctx=document.getElementById(name).getContext('2d');
this.canvas=this.ctx.canvas;
this.width=this.canvas.width;
this.height=this.canvas.height;
this.layers=new Array();
this.draw = function() {
this.canvas.width = this.canvas.width;
this.ctx.beginPath();
this.ctx.rect(0,0,this.width,this.height);
this.ctx.closePath();
this.ctx.fillStyle="#eaeaea";
this.ctx.fill();
this.ctx.beginPath();
this.ctx.rect(250,50,300,250);
this.ctx.closePath();
this.ctx.fillStyle="#ff0000";
this.ctx.fill();
intensity=Math.random();
this.flash(intensity);
};
this.flash = function(intensity) {
this.ctx.globalAlpha = intensity;
this.ctx.beginPath();
this.ctx.rect(0,0,this.width,this.height);
this.ctx.closePath();
this.ctx.fillStyle="#fff";
this.ctx.fill();
this.ctx.globalAlpha = 1;
setInterval(this.draw.bind(this),1000);
};
function initCanvas(){
mycanvas=new Canvas('myCanvas');
mycanvas.draw();
}
$(document).ready(function() {
initCanvas();
});
Solution found:
Use setTimeout instead of setInterval.

Close all the paths, which you open:
this.draw = function() {
this.canvas.width = this.canvas.width;
this.ctx.beginPath();
this.ctx.rect(0,0,this.width,this.height);
this.ctx.closePath(); //Closing
this.ctx.fillStyle="#eaeaea";
this.ctx.fill();
this.ctx.beginPath();
this.ctx.rect(250,50,300,250);
this.ctx.closePath(); //Closing
this.ctx.fillStyle="#ff0000";
this.ctx.fill();
this.flash(40);
};
this.flash = function(intensity) {
this.ctx.globalAlpha = intensity;
this.ctx.beginPath();
this.ctx.rect(0,0,this.width,this.height);
this.ctx.closePath(); //Closing
this.ctx.fillStyle="#fff";
this.ctx.fill();
this.ctx.globalAlpha = 1;
setInterval(this.draw.bind(this),1000);
};

You've got a massive memory leak because you keep using setInterval in the flash function. Let's look at the sequence of events
mycanvas object created
draw()
draw calls flash
flash sets an interval to call draw every second
draw calls flash and sets another interval
Process repeats till you've got a lot of intervals calling draw
To solve it, use setTimeout in flash. So it calls draw after a second, which calls flash and then calls draw again in a second. Also, 1000ms won't give you 12fps. 1000/12 will.
Also, use ctx.closePath(); to close the paths you opened with beginPath()
You also never closed the Canvas function with a }.
Here's a demo

I don't know if this is relevant anymore, but I found myself in a similar situation and wanted to give an even better answer.
Use requestAnimationFrame(yourLoop), especially for games since it is faster and has better performance.
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/

Related

How to animate the canvas with js

I'm trying to make an animation using html canvas, but I don't know if I have the best approach to it.
What I'm trying is to make a <canvas id="canvas"></canvas> and draw a rectangle on it using the fillRect().
Then I'm executing a function onload, that has a timeout of 500 miliseconds.
The function basically draws the rectangle 1px to where I want, by changing its x or y, and then, with clearRect(), I'm crealing the rectangle some time after starting on the starting point and following the other one.
Am I doing this right? or is there a better way to approach it?
You can use this structure
const canvas = document.getElementById('can');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.backgroundColor = 'white';
var someconstructorName = function(paramA,paramB,...){
"Initialisation of variables and other things"
this.draw = function(){
"your logic"
}
this.update = function(){
"your update logic"
this.draw();
}
}
function animate(){
requestAnimationFrame(animate)
}
animate();
Check out this Pen here it will give you a good idea:
https://codepen.io/khanChacha/pen/YgpBxM
If you have to use this style multiple times, I recommend diving into AnimeJS, a JS library which makes animating a lot simpler. ^^
AnimeJS
It supports delays and timelines too, which seems to be what you are using right now ^^

Android 4.2 Stock browser. Canvas clearRect() sometimes doesn't work

So I have a problem with clearRect in js-canvas-animation. This problem happens only on Android API 16, and only in the moment when animation is restart.
I'm using setInterval() for my animation (here is simplify code)
function start() {
clearInterval(animationInterval);
x = 0;
canvas = document.getElementById("animationCanvas");
ctx = canvas.getContext("2d");
animationInterval = setInterval(func, 30);
}
function func() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(halfWidth - x, 0, 250, 150);
x += extensionStep;
}
It is look like a shore. But every time, when i restart animation with help start function, under new animation i see last frame previous animation. Already i was trying beginPath(), save, stroke. I check all question on SO and nothing.
How i can clear the background under animation?
So after several hours, i found one of the solves. It is strange bug for old android, because new android (i checked on api 24) it fixed. For hard cleaning you can detach canvas from DOM and reattach again:
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.style.display = 'none';// Detach from DOM
canvas.offsetHeight; // Force the detach
canvas.style.display = 'inherit'; // Reattach to DOM
It is simple operation and is not resource intensive.

Making a 'History Panel' for a paint program

I'm making a very simple 'pixel-painting' program using HTML5/Canvas.
I'd like to give the user an option to go back in 'history', like the History panel in Photoshop / Adobe programs.
Basically it would be an undo button, but you'd be able to go back to the start of your actions, and there would also be a log showing the details of each action.
Is this possible? How would I even start storing this data?
How much memory is available within the Chrome browser in order to allow this on one page? – (Sorry if that is silly to ask, still quite new to Javascript and working within the browser.)
I have read this undo button Question, which is similar but I'd like to make the info about data being stored visible.
Thank you so so much for any help you can give!
You would need to build a simple undo-redo stack. Then you need to decide if you will store vector data or image data. The latter is more efficient but can also take up much more memory. You may have cases where you want to store both types of data (path on top of images).
The method would be in simple steps:
Store initial state for the new document. Keep a stack pointer pointing to the next free slot (for example using an array).
When mouse down is hit (or some other operation that will cause a change is started) move stack pointer forward.
When mouse button is released, make a snapshot, create thumbnail etc. It's up to you if you want to store the drawing as points or as a bitmap. If bitmap data you can get around storage space by compressing it using for example zip. Move stack pointer forward. If there exists snapshots in the stack at this point remove them.
When you need to undo, simply draw back previous stored step and move the stack pointer back. By keeping the snapshots you can do redo by moving stack pointer forward and redraw the snapshot, if any.
And finally, to visualize the undo-redo stack you can simply render each snapshot to a separate canvas at scale and extract that as an image which you put in the list.
Note: when creating a undo state it's important to clear any snapshots after the new stack pointer position. This is because if undo has been used, redo can be used if no changes. However, if undo was used and new drawing was added this would invalidate the next states so they have to be removed.
As to browser memory it will depend on the user's system. Some have a few gigabytes, other has a lot. There is no way to know. You would have to chose a UX strategy suitable for your scenario as well as target audience.
Example
This does not implement the logistics for handling the sync of the thumbnails, but has most other parts. I'll leave the rest as an exercise.
var ctx = c.getContext("2d"),
stack = [], // undo-redo stack
sp = 0, // stack pointer
isDown = false; // for drawing (demo)
capture(); // create an initial undo capture (blank)
ctx.lineCap = "round"; // setup line for demo
ctx.lineWidth = 4;
// simple draw mechanism
c.onmousedown = function(e) {
sp++; // on mouse down, move stack pointer to next slot
isDown = true; // NOTE: clear any snapshots after this point (not shown)
var pos = getXY(e); // start drawing some line - how you draw is up to you
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmousemove = function(e) {
if (!isDown) return; // only for drawing
var pos = getXY(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmouseup = function() {
if (!isDown) return;
isDown = false;
capture(); // capture an undo state
makeThumb(); // create and insert a thumbnail of state
};
function capture() {
stack[sp] = c.toDataURL(); // one way, you could use getImageData,
// or store points instead.. it's up to you
}
// Creates a thumbnail of current canvas and insert into visible undo stack
function makeThumb() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 64;
var ctxTmp = canvas.getContext("2d");
ctxTmp.drawImage(c, 0, 0, canvas.width, canvas.height);
undos.appendChild(canvas);
}
// UNDO button clicked
undo.onclick = function() {
var img = new Image; // restore previous state/snapshot
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer back and get previous snapshot
if (sp > 0) img.src = stack[--sp];
};
// REDO button clicked
redo.onclick = function() {
// anything we can redo?
if (sp < stack.length) {
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer forward and get next snapshot
img.src = stack[++sp];
}
};
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#c {background:#ccc}
<button id=undo>Undo</button>
<button id=redo>Redo</button><br>
<canvas id=c width=500 height=500></canvas>
<div id=undos></div>
You could copy the current canvas to a separate one each time an action is performed. Simply displaying old canvases could serve as an action log.
You can send a canvas to drawImage directly:
destContext.drawImage( srcCanvas, 0, 0 );
If that approach consumes too much memory, an alternative is to store all the commands in a stack, remove the last element when undoing, and redraw everything from scratch.

How to access processing.js 'width' and 'height' outside processing.js functions

I've recently made the leap from using Khan Academy's processing.js environment to the real deal and am getting a little confused.
I have a simple processing.js program that basically draws a circle, and I want the size of this circle to be determined by the width of the canvas.
If I print the width within a processing.js function, like setup, I'm shown the correct 500px width. Unfortunately, whenever I try to access the width property outside of a processing.js function, it shows the default 100px size, even though the canvas itself is 500px wide.
I think I might be using a fairly ugly mix of processing and javascript, which could be the root of my problems. Your help would be much appreciated!
Processing.js
///* PROCESSING.JS SETUP *///
void setup() {
size(500, 500);
println(width); // WORKS! :)
}
println(width); // DOESN'T WORK... :(
///* GLOBAL VARIABLES *///
var moleculeQuantity = 1;
///* OBJECT CONSTUCTORS *///
var Molecule = function(x, y) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
Molecule.prototype.draw = function() {
noStroke();
fill(88, 91, 183);
ellipse(this.x, this.y, 70, 70);
fill(225, 227, 228);
ellipse(this.x, this.y, 40, 40);
};
// Fill array with molecule objects
var molecules = [];
for (var i = 0; i < moleculeQuantity; i++) {
molecules[i] = new Molecule(200, 100);
}
///* DRAW FUNCTION *///
void draw() {
background(225, 227, 228);
molecules[0].draw();
}
Your problem has nothing to do with mixing Processing and JavaScript, and it has nothing to do with asynchronous execution. It's much simpler than that.
Think of the order your code executes in. Anything outside of a method will execute before your setup() function is called. In your case, that means you're accessing the width variable before you've changed it by calling the size() function.
You have to change your code so that your code is triggered after setup() is called. The simplest way to do that is to just move your code to the end of your setup() function, or into a function that's called after setup(), such as the draw() or event methods.
You might think that because your function call is below the setup() function in your code that the setup() call happens first, but it doesn't. You've simply defined the setup() function- it hasn't been called (by Processing) yet! Try moving any code that's outside of a function to the top of your sketch to make it more obvious:
println(width); // this happens first, so width is still 100
void setup() {
size(500, 500);
println(width); //now that size() has been called, the width is 500
}
Edit: I'll try to explain the order of events. Here is what happens when you load a page that contains a Processing.js sketch:
The page is loaded.
Processing.js itself is loaded.
Processing.js compiles your Processing code into JavaScript code.
Your code (which is now JavaScript code) is loaded. Functions like setup() and draw() are defined at this step, but not called yet. Code outside of your functions is called. This is when you see 100 being printed out.
Processing.js calls the setup() function that was defined in step 4. This is when the width is set.
Processing.js starts calling the draw() function 60 times per second.
As for where you should place your variables and functions, that completely depends on what you want to do with them. But you might place a variable's declaration at the top of your sketch and its initialization inside the setup() function. That way you can access its value anywhere, but you know it won't be set until setup() has run. Something like this:
float middleX;
void setup(){
size(500, 500);
middleX = width/2;
}
void draw(){
background(0);
ellipse(middleX, mouseY, 10, 10);
}

Multiple requestAnimationFrame performance

If I’m doing multiple animations, is it OK performance-wise to add multiple requestAnimationFrame callbacks? F.ex:
function anim1() {
// animate element 1
}
function anim2() {
// animate element 2
}
function anim3() {
// animate element 3
}
requestAnimationFrame(anim1);
requestAnimationFrame(anim2);
requestAnimationFrame(anim3);
Or is it proven worse than using a single callback:
(function anim() {
requestAnimationFrame(anim);
anim1();
anim2();
anim3();
}());
I’m asking because I don’t really know what is going on behind the scenes, is requestAnimationFrame queuing callbacks when you call it multiple times?
I don't think any of these answers really explained what I was looking for: "do n calls to requestAnimationFrame" get debounced (i.e. dequeued 1 per frame) or all get invoked in the next frame.
When callbacks queued by requestAnimationFrame() begin to fire multiple callbacks in a single frame (mdn)
This suggests the latter, multiple callbacks can be invoked in the same frame.
I confirmed with the following test. A 60 hz refresh rate translates to a 17ms period. If it were the former, no 2 timestamps would be within 17ms of each other, but that was not the case.
let sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let update = async timestamp => {
console.log('update called', timestamp)
await sleep(10);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
You should be using only one requestAnimationFrame call as calls to requestAnimationFrame do stack. The single callback version is thus more performant.
Someone benchmarked this. Let's talk...
https://jsperf.com/single-raf-draw-calls-vs-multiple-raf-draw-calls
I looked at the performance comparison (you should too). You're welcome to disagree. These are drawing primitives on a canvas element.
function timeStamp() {
return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
}
function frame() {
drawCircle();
drawLines();
drawRect();
}
function render() {
if (timeStamp() >= (time || timeStamp())) {
time = timeStamp() + delayDraw;
frame();
}
requestAnimationFrame(render);
}
function render1() {
if (timeStamp() >= (time || timeStamp())) {
time = timeStamp() + delayDraw;
drawCircle();
}
requestAnimationFrame(render1);
}
function render2() {
if (timeStamp() >= (time || timeStamp())) {
time = timeStamp() + delayDraw;
drawRect();
}
requestAnimationFrame(render2);
}
function render3() {
if (timeStamp() >= (time || timeStamp())) {
time = timeStamp() + delayDraw;
drawLines();
}
requestAnimationFrame(render3);
}
I think this code is really benchmarking 7 calls to timestamp() vs 2 calls to timestamp(). Look at the difference between Chrome 46 and 47.
Chrome 46: 12k/sec (one call) vs 12k/sec (3 calls)
Chrome 47: 270k/sec (one call) vs 810k/sec (3 calls)
I think this is so well optimized that it doesn't make a difference. This is just measuring noise at this point.
My takeaway is this doesn't need to be hand-optimized for my application.
If you're worried about performance look at the difference between Chrome 59 (1.8m ops/sec) vs Chrome 71 (506k ops/sec).
The requestAnimationFrame binds a function call and returns the frameID. Requesting multiple frames is NOT the same like adding multiple event listeners to an event- each of your functions is called in another frame.
So if you continuously (each function recalls itself recursively) request several frames you're loosing the benefit that all updates are rendered within one frame. So even if there is a high framerate animations may not look that smooth.
But: you can only use cancelAnimationFrame(frameID) for all methods and may need some extra code to cancel single animations

Categories

Resources