I am learning JavaScript, coming from eight years of Python experience, and as an exercise I made a Mandelbrot renderer that generates the fractal and uses putImageData to update the canvas from left to right one single-pixel column at a time.
I found that with this approach the visible image in the browser only updates when the full-screen calculation is complete (rather than seeing it appear gradually from left to right as I wanted). I understand that it is the expected behaviour, so I decided to add the "scan-line" animation by using requestAnimationFrame. (something like seen here: Christian Stigen Larsen renderer)
My expectation is that lighter calculations should render faster (as the next frame is available sooner) and higher-iteration calculations should render slower. What I found with my implementation is that the scan-line is consistently slow.
I have isolated this issue from anything to do with my Mandelbrot calculations, as this behaviour also happens for the minimal case below. I am running this on Chrome 83 and see the canvas populate very slowly at a constant rate (approx 30 pixels per second) from left to right.
Is my implementation of rAF incorrect or are my expectations wrong? The renderer I linked to uses setTimeout() to animate, but I read that it is a widely discouraged practice these days.
How should I implement a left-to-right scan update of my canvas at the highest available frame-rate (I am not worried about limiting it at the moment)?
EDIT: For clarity, the code below draws a single thin rectangle at every frame request by rAF, and takes exactly the same amount of time to paint the full canvas as a 100-iteration Mandelbrot render.
This suggest to me that the slowness of it is not due to amount of calculations taking place between frames.
const canvas = document.querySelector('.myCanvas');
const width = window.innerWidth;
const height = window.innerHeight;
const ctx = canvas.getContext('2d');
function anim(timestamp, i) {
if (i < width) {
ctx.fillRect(i, 0, 1, height);
window.requestAnimationFrame(function(timestamp) {
anim(timestamp, i + 1);
})
}
}
ctx.fillStyle = 'rgb(0,0,0)';
window.requestAnimationFrame(function(timestamp) {
anim(timestamp, 0);
});
<!DOCTYPE html>
<html lang="en">
<head>
<canvas class="myCanvas">
<p>Add suitable fallback here.</p>
</canvas>
<script src="slow%20rAF.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<meta charset="UTF-8">
<title>Mandelbrot</title>
</head>
<body>
</body>
</html>
There is no 'faster' requestAnimationFrame.
Generally the answer to this is to separate calcuations from rendering. Ideally the calculations happen off of the main thread and rendering is handled on the main thread. Even without threading you should be able to find a way to do the calculations outside of the RAF, and just have the RAF render the pixels.
Related
I have been developing a program which includes some sort of genetic algorithm. For my program, let's say there is a population of 200 units, and each unit can be in 5 different states. Inititlly, they all start at state 0, and they can randomly jump to states 1 to 4, and influence other units to jump as well. This way, the more units are on state 2, the more units will jump to state 2 and so on. I have these units moving randomly inside my canvas, bouncing off the walls when they hit them.
The one thing I want to do now is visualize the evolution on a chart, and for that I would like to have the canvas with the units jumping states on one side and the chart next to it, dynamically representing the percentage of units in state 0, 1, 2... simultaneously. I will presumably have no problem in coding the chart, however I cannot find a way of displaying it outside the canvas or without altering it.
Just in case, I am programming in Atom and have mostly used p5 libraries.
Any ideas??
You have 2 options:
Make a second canvas (Like enhzflep said), but this might be complicated for you, becuase you will not have access to P5.js drawing tools on that second canvas, look at this:
(On your first canvas)
fill(255,0,0)
rect(50,50,50,50);
To make and draw to a second canvas:
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
//deal with positioning, scaling, and other stuff (comment if you need help with that)
...
const c = canvas.getContext('2d');
c.fillStyle = "rgb(255,0,0)";
c.fillRect(50,50,50,50);
(See, lots of effort)
Or, you can just use your first canvas, and partition a section off that is dedicated to the graph
createCanvas(600 + graphWidth, 600);
//Wherever your bouncing off walls code is
//for the right side of the screen
if(this.x > width - graphWidth){
bounce();
}
//that leaves you a graphWidth by 600 rectangle for you to draw you graph
The second option is much easier to read and will save you some headaches (I would use that).
I want to measure the performance of browsers from my JavaScript when they render the HTML5 Canvas.
To begin, I drew a simple 2D canvas and logged the time right before and after it was done:
HTML
<canvas id="myCanvas" width="200" height="100"></canvas>
JS
var start = performance.now();
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.moveTo(0, 0);
ctx.lineTo(200, 100);
ctx.stroke();
var end =performance.now();
alert(end-start); //alert for testing only
This returned a very small value as predicted.
To mesure the performance I should render something more complex, So i did a code that renders a 3D image and animates it.
But now It's tricky to calculate time before and after since the animation is on a constant loop. I tried logging the time for the first animation before it continued to loop, but still the time difference is very small.
I want to see if I am in the right direction or if there are better practices. My main aim is to check differences in performance between as many browsers as possible, using javascript, and I thought this was better logged while doing a complex task such as renderign a 3D canvas.
What I'd like to do is draw my graphics on a buffer and then be able to copy it as is to the canvas so I can do animation and avoid flickering. But I couldn't find this option. Anyone know how I can go about this?
A very simple method is to have two canvas-elements at the same screen location and set visibility for the buffer that you need to show. Draw on the hidden and flip when you are done.
Some code:
CSS:
canvas { border: 2px solid #000; position:absolute; top:0;left:0;
visibility: hidden; }
Flipping in JS:
Buffers[1-DrawingBuffer].style.visibility='hidden';
Buffers[DrawingBuffer].style.visibility='visible';
DrawingBuffer=1-DrawingBuffer;
In this code the array 'Buffers[]' holds both canvas-objects. So when you want to start drawing you still need to get the context:
var context = Buffers[DrawingBuffer].getContext('2d');
The following helpful link, in addition to showing examples and advantages of using double buffering, shows several other performance tips for using the html5 canvas element. It includes links to jsPerf tests, which aggregate test results across browsers into a Browserscope database. This ensures that the performance tips are verified.
https://web.dev/canvas-performance/
For your convenience, I have included a minimal example of effective double buffering as described in the article.
// canvas element in DOM
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d');
// buffer canvas
var canvas2 = document.createElement('canvas');
canvas2.width = 150;
canvas2.height = 150;
var context2 = canvas2.getContext('2d');
// create something on the canvas
context2.beginPath();
context2.moveTo(10,10);
context2.lineTo(10,30);
context2.stroke();
//render the buffered canvas onto the original canvas element
context1.drawImage(canvas2, 0, 0);
Browsers I've tested all handle this buffering for you by not repainting the canvas until the code that draws your frame has completed. See also the WHATWG mailing list: http://www.mail-archive.com/whatwg#lists.whatwg.org/msg19969.html
You could always do
var canvas2 = document.createElement("canvas");
and not append it to the DOM at all.
Just saying since you guys seem so obsessed with display:none;
it just seems cleaner to me and mimicks the idea of double buffering way more accurately than just having an awkwardly invisible canvas.
More than two years later:
There is no need for 'manually' implement double buffering. Mr. Geary wrote about this in his book "HTML5 Canvas".
To effectively reduce flicker use requestAnimationFrame()!
For the unbelievers, here's some flickering code. Note that I'm explicitly clearing to erase the previous circle.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw_ball(ball) {
ctx.clearRect(0, 0, 400, 400);
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.arc(ball.x, ball.y, 30, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
var deltat = 1;
var ball = {};
ball.y = 0;
ball.x = 200;
ball.vy = 0;
ball.vx = 10;
ball.ay = 9.8;
ball.ax = 0.1;
function compute_position() {
if (ball.y > 370 && ball.vy > 0) {
ball.vy = -ball.vy * 84 / 86;
}
if (ball.x < 30) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
} else if (ball.x > 370) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
}
ball.ax = ball.ax / 2;
ball.vx = ball.vx * 185 / 186;
ball.y = ball.y + ball.vy * deltat + ball.ay * deltat * deltat / 2
ball.x = ball.x + ball.vx * deltat + ball.ax * deltat * deltat / 2
ball.vy = ball.vy + ball.ay * deltat
ball.vx = ball.vx + ball.ax * deltat
draw_ball(ball);
}
setInterval(compute_position, 40);
<!DOCTYPE html>
<html>
<head><title>Basketball</title></head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
</body></html>
Josh asked (a while back) about how the browser knows "when the drawing process ends" so as to avoid flicker. I would have commented directly to his post but my rep isn't high enough. Also this is just my opinion. I don't have facts to back it up, but I feel fairly confident about it and it may be helpful to others reading this in the future.
I'm guessing the browser doesn't "know" when you're done drawing. But just like most javascript, as long as your code runs without relinquishing control to the browser, the browser is essentially locked up and won't/can't update/respond to its UI. I'm guessing that if you clear the canvas and draw your entire frame without relinquishing control to the browser, it won't actually draw your canvas until you're done.
If you set up a situation where your rendering spans multiple setTimeout/setInterval/requestAnimationFrame calls, where you clear the canvas in one call and draw elements on your canvas in the next several calls, repeating the cycle (for example) every 5 calls, I'd be willing to bet you'd see flicker since the canvas would be updated after each call.
That said, I'm not sure I'd trust that. We're already at the point that javascript is compiled down to native machine code before execution (at least that's what Chrome's V8 engine does from what I understand). I wouldn't be surprised if it wasn't too long before browsers started running their javascript in a separate thread from the UI and synchronizing any access to UI elements allowing the UI to update/respond during javascript execution that wasn't accessing UI. When/if that happens (and I understand there are many hurdles that would have to be overcome, such as event handlers kicking off while you're still running other code), we'll probably see flicker on canvas animation that aren't using some kind of double-buffering.
Personally, I love the idea of two canvas elements positioned over top of each other and alternating which is shown/drawn on each frame. Fairly unintrusive and probably pretty easily added to an existing application with a few lines of code.
There is no flickering in web browsers! They already use dbl buffering for their rendering. Js engine will make all your rendering before showing it. Also, context save and restore only stack transformational matrix data and such, not the canvas content itself.
So, you do not need or want dbl buffering!
Rather than rolling your own, you're probably going to get the best mileage by using an existing library for creating clean and flicker-free JavaScript animation:
Here's a popular one: http://processingjs.org
you need 2 canvas: (notice the css z-index and position:absolute)
<canvas id="layer1" width="760" height="600" style=" position:absolute; top:0;left:0;
visibility: visible; z-index: 0; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<canvas id="layer2" width="760" height="600" style="position:absolute; top:0;left:0;
visibility: visible; z-index: 1; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
you can notice that the first canvas is visible and the second it's hidden the idea it's to draw on the hidden after that we will hide the visible and make the hidden canvas visible. when it's hidden 'clear hidden canvas
<script type="text/javascript">
var buff=new Array(2);
buff[0]=document.getElementById("layer1");
buff[1]=document.getElementById("layer2");
ctx[0]=buff[0].getContext("2d");
ctx[1]=buff[1].getContext("2d");
var current=0;
// draw the canvas (ctx[ current ]);
buff[1- current ].style.visibility='hidden';
buff[ current ].style.visibility='visible';
ctx[1-current].clearRect(0,0,760,600);
current =1-current;
In most situations, you don't need to do this, the browser implements this for you. But not always useful!
You still have to implement this when your drawing is very complicated.
Most of the screen update rate is about 60Hz, it means the screen updates per 16ms. The browser's update rate may near this number. If your shape need 100ms to be completed, you'll see a uncompleted shape. So you can implement double buffering in this situation.
I have made a test: Clear a rect, wait for some time, then fill with some color. If I set the time to 10ms, I won't see flickering. But if I set it to 20ms, the flickering happens.
Opera 9.10 is very slow and shows the drawing process. If you want to see a browser not use double buffering, try Opera 9.10 out.
Some people suggested that browsers are somehow determining when the drawing process ends but can you explain how that can work? I haven't noticed any obvious flicker in Firefox, Chrome, or IE9 even when the drawing is slow so it seems like that is what they are doing but how that is accomplished is a mystery to me. How would the browser ever know that it is refreshing the display just before more drawing instructions are to be executed? Do you think they just time it so if an interval of more than 5ms or so goes by without executing a canvas drawing instruction, it assumes it can safely swap buffers?
This is an example of double buffering with image data.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 Canvas Demo of PixelBuffer</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id="buffer1"></canvas>
<script>
const canvas = document.getElementById('buffer1');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(100, 100);
const pixelBuffer = new Uint32Array(imageData.data.buffer);
for (let i = 0; i < pixelBuffer.length; i++) {
pixelBuffer[i] = 0xFF0000FF;
}
ctx.putImageData(imageData, 0, 0);
</script>
</body>
</html>
i have some problems with webkit (chrome/safari) and Canvas with large images.
For my customer we had to write an animated intro movie with changing images in 80ms per frame on a canvas.
There are 130 frames and we had to put the single images into 4 sprites to reduce the single-request load of the page.
Every sprite size is about 2,5MB and we just slice the part of the needed frame. So far so possible.
don't want to bother you with to much code. But in the end it's just about:
this.$context.drawImage(img, 0, top, img.width-1 ,(img.height / sequenceCount)-1, 0, 0, this.$canvas.width, this.$canvas.height);
witch is called within 80ms timeouts.
We did that and it works on all major browser. But with webkit every change to the next sprite causes a hard lag of about 400ms.
The same issue was with IE9 but it could be fixed with drawing every sprite once in the beginning
if (Browser.ie9) {
for(var x = 0; x < this.sequence[0].sprites.length; x++){
this.draw(this.sequence[0].sprites[x].obj, 0, 1);
}
this.$context.clearRect(0, 0, this.$canvas.width, this.$canvas.height);
}
(The draw function include just the drawImage function from the example before.)
But with webkit browsers i get still this lagging time of ~400ms.
Yes, the images are preloaded. So this is not the issue.
Any ideas what this could be?
Thanks for your help!
Every time you get the canvas or image width, you are accessing the DOM, which is generally slower than accessing JS memory. You might see an improvement if you store those values on resize, and use the stored value instead.
var canvasWidth, canvasHeight, imgWidth, imgHeight;
I've tried to search through StackOverflow for a similar question, but I seem to only be able to find questions that are either "how do I draw text in <canvas>" (answer: fillText) or "how do I make crisp lines" (answer, 0.5 coordinate offsets, because canvas isn't an integer pixel grid), neither of which answer my question. Which is the following:
How do I get fillText to render text as crisp as the browser renders text as part of a normal page?
As a demonstration, take the page on http://processingjs.nihongoresources.com/fontreftest/crisptest, which uses the following html+js (using html5 doctype):
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>fillText</title>
<style type="text/css">
canvas { margin: 0px; padding: 0px; display: block; }
</style>
</head>
<body>
<table><tr><td style="font: normal normal 400 12px/14px Arial">
<div>This is some text in Arial.</div><div>This is some text in Arial.</div>
<div>This is some text in Arial.</div><div>This is some text in Arial.</div>
<div>This is some text in Arial.</div><div>This is some text in Arial.</div>
<div>This is some text in Arial.</div><div>This is some text in Arial.</div>
<div>This is some text in Arial.</div><div>This is some text in Arial.</div>
</td><td>
<canvas id="filltext0"></canvas><canvas id="filltext1"></canvas>
<canvas id="filltext2"></canvas><canvas id="filltext3"></canvas>
<canvas id="filltext4"></canvas><canvas id="filltext5"></canvas>
<canvas id="filltext6"></canvas><canvas id="filltext7"></canvas>
<canvas id="filltext8"></canvas><canvas id="filltext9"></canvas>
</td><tr></table>
<script type="text/javascript"><!--
function drawTextIn(which)
{
var tstring = "This is some text in Arial",
canvas = document.getElementById('filltext' + which),
context = canvas.getContext("2d");
var fontCSS = "normal normal 400 12px/14px Arial";
context.font = fontCSS;
canvas.width = context.measureText(tstring).width;
canvas.height = 14;
// setting the width resets the context, so force the font again
context.font = fontCSS;
context.fillText(tstring,0 + i/10,12);
}
for(var i=0; i<10; i++) { drawTextIn(i); }
--></script>
</body>
</html>
This will generate a side-by-side view of ten lines rendered by the browser in Arial at 12px with a lineheight (size+leading) of 14px, in normal style and weight, and ten lines rendered with the same font properties using <canvas> elements, with every line calling fillText with a 0.1 shifted x-coordinate. The first line has x coordinate 0, the second 0.1, the third 0.2, etc.
No current browser that supports <canvas> that I have tried (Chrome 12, Firefox 5, Opera 11.50, Internet Explorer 9, Safari 5) renders the text the same as real text, regardless of x-coordinate shift.
So the main thing I'm now wondering is whether or not there exists a (normal, fast) way to make a browser draw text to a <canvas> with the same crispness as it draws text to the page when it's just placing text.
I've read http://diveintohtml5.ep.io/canvas.html#text, http://blog.thesoftwarefactory.be/2009/04/drawing-crisp-lines-on-html5-canvas.html, http://joubert.posterous.com/crisp-html-5-canvas-text-on-mobile-phones-and, and http://www.bel.fi/~alankila/lcd/ - none of these cover the subject. The last offers an interesting approach, but one that can only be used if the canvas is empty, which is too limited to be of real-world use, neat as it is (it's too expensive to construct a new canvas for every line of text, call fillTExt and then clean it up using scanlines, and then copy the text over with alpha blending).
So.... can this be done? If you know how, I'd really, really like to know how to achieve this. And if you think the answer is no: please, include links to official specs that demonstrate why that answer is the right answer... I need the peace of mind of an official spec saying I can't do what I want, or I'll just keep wondering whether some even smarter person might have answered this question with "yes" =P
So the main thing I'm now wondering is whether or not there exists a (normal, fast) way to make a browser draw text to a with the same crispness as it draws text to the page when it's just placing text.
The short answer is sadly no. Different browsers are currently implementing subpixel rendering/anti-aliasing for text in wildly different ways, which leads to (for some browsers) their rendered HTML page text looking good but their Canvas text looking awful.
One possibility is to pre-render all of your text (Even if that means writing it on an HTML page and taking a screenshot) and using drawImage. This also has the benefit of being much faster than calls to drawText, but of course is not very dynamic at all.
While the question is old, I came across the exact same issue. While digging in FireFox, I found a flag for canvas which did the trick: mozOpaque. When set, the font uses the correct LCD rendering and the text is crisp as other elements on the page. To test it, just put:
canvas.mozOpaque = true;
after your canvas creation. There is a catch: the canvas can't be transparent anymore, but it wasn't an issue for my use. May be Chrome/Safari/IE got something similar. I hope this helps !