staggering iterations over setTimeout - javascript

I'm trying to stagger for loops over time to draw a grid and keep system load down. For example: for a 100x100 grid that would be 10,000 iterations, instead of doing them all instantly, I want to do 1,000 then wait 250ms and carry on the next 1,000 until it's complete.
I can't seem to figure out why it isn't working.
In this example i am trying to do just that, but it only draws the first 1,000 squares, but console.log('iterate'); still runs and the iteration values follow on!
http://jsfiddle.net/FMJXB/2/
In this example, i have removed the setTimeout and instead calling the function twice, to simulate 2 loops with instant effect, and that draws 2,000 squares!
http://jsfiddle.net/FMJXB/3/
Why does having the function in a setTimeout stop the code from working?

Here’s a coding pattern to stagger drawing iterations using setTimeout
The logic goes like this:
Use variable ”j” to track horizontally draws across your grid.
Use variable “i” to track vertical draws down your grid.
Use variable “iterations” to cut the drawing into blocks of 1000 draws at a time
When iterations has finished it’s 1000 draws, call setTimeout.
This code is for illustration—you will need to adapt it for your easelJS specific needs.
function draw(){
// reset iterations for this block of 1000 draws
var iterations = 0;
// loop through 1000 iterations
while(iterations++<1000){
// put drawing code here
graphics.drawRect(pixl.pixelSize*j, pixl.pixelSize*i, pixl.pixelSize, pixl.pixelSize);
// increment i and j
if(++j>canvas.width-1){ i++; j=0; }
// if done, break
if(i*j>pixelCount) { break; }
}
// schedule another loop unless we've drawn all pixels
if(i*j<=pixelCount) {
setTimeout(function(){ draw(); }, 1000);
}
}
Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/Z3fYG/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:20px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var i=0;
var j=0;
var pixelCount=canvas.width*canvas.height;
draw();
function draw(){
ctx.beginPath();
var iterations = 0;
while(iterations++<1000){
// put drawing code here
ctx.rect(j,i,1,1);
// increment i and j
if(++j>canvas.width-1){ i++; j=0; }
// if done, break
if(i*j>pixelCount) { break; }
}
ctx.fill();
// schedule another loop unless we've drawn all pixels
if(i*j<=pixelCount) {
setTimeout(function(){ draw(); }, 1000);
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas width="100" height="100" id="canvas"></canvas>
</body>
</html>

Related

completing calculations in background using javascript callbacks

I am attempting to use a callback to create an asynchronous function that does calculations behind a draw loop without slowing it down. I have read many callback examples and am clearly doing something wrong.
I have created a simplified version of what I would like to do. When you click your mouse it should do the math without hanging up the draw loop. Right now it causes a hangup:
var nPoints = 1;
var sumDone = false;
var sum = 0;
function setup() {
var myCanvas = createCanvas(200, 200);
myCanvas.parent("p5");
}
function draw(){
nPoints = nPoints+1
stroke(0,0,0);
background(245,245,245);
noFill();
rect(1,1,198,198);
textAlign(CENTER);
fill(0,0,0);
textSize(20);
if(sumDone){
text(sum,100,20);
}else{
text("not done",100,20);
}
noStroke();
push();
translate(100,100);
rotate(nPoints/50);
rect(-50,-10,100,20);
pop();
}
function mouseClicked(){
if(sumDone){
sumDone = false;
sum=0;
}else{
doMath(function (){
sumDone = true;
});
}
}
function doMath(callback){
for(var i=0;i<10000000;i++){
sum = sum + i;
}
callback();
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<body>
<div id="p5" align="center">
</div>
<script>
<!-- JAVASCRIPT CODE GOES HERE -->
</script>
</body>
As you can see, the math still completely hangs up the draw loop. Is it possible to do this in a way where the draw loop is not effected?
JavaScript is single-threaded. Moving the work to a callback function just ties up the thread that's used to run draw() and every other JavaScript function.
The only way to offload work like this is to do it on the server and use AJAX to query for the result.
Other than that, perhaps try breaking the calculation down into smaller chunks. Can you do 10% of the calculation per frame or something?
Edit: These threads mention the concept of web workers which you might look into.

setTimeout() not working in animation function | Animation not working

I'm trying to build a very simple animation function. I'm using this tutorial to build my project:
https://www.youtube.com/watch?v=hUCT4b4wa-8
The result after the button is clicked should be a green box moving across the page from left to right. When the button is clicked, nothing happens and I don't get any console errors.
Here's my fiddle:
https://jsfiddle.net/xkhpmrtu/7/
And here's a snippet of my code:
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<style type="text/css">
canvas {
border: 1px solid #666;
}
</style>
<script type="application/javascript" language="javascript">
function anim(x,y) {
var canvas = document.getElementById('canvas');//reference to canvas element on page
var ctx = canvas.getContext('2d');//establish a 2d context for the canvas element
ctx.save();//save canvas state if required (not required for the tutoriral anaimation, but doesn't hurt the script so it stays for now)
ctx.clearRect(0, 0, 550, 400);//clears the canvas for redrawing the scene.
ctx.fillStyle = "rgba(0,200,0,1)";//coloring the rectangle
ctx.fillRect = (x, 20, 50, 50);//drawing the rectangle
ctx.restore();//this restores the canvas to it's original state when we saved it on (at the time) line 18
x += 5; //increment the x position by some numeric value
var loopTimer = setTimeout('draw('+x+','+y+')', 2000);// setTimeout is a function that
</script>
</head>
<body>
<button onclick="animate(0,0)">Draw</button>
<canvas id="canvas" width="550" height="400"></canvas>
</body>
Any idea what I'm doing wrong?
I just had a look at the tutorial link. I will give if a major thumbs down as it demonstrates how not to animate and how not to do many other things in Javascript.
First the script tag and what is wrong with it
// type and language default to the correct setting for javascrip
// <script type="application/javascript" language="javascript">
<script>
function anim(x,y) {
// get the canvas once. Getting the canvas for each frame of an
// animation will slow everything down. Same for ctx though will not
// create as much of a slowdown it is not needed for each frame
// var canvas = document.getElementById('canvas');
// var ctx = canvas.getContext('2d');
// Dont use save unless you have to. It is not ok to add it if not needed
// ctx.save();
// dont use literal values, canvas may change size
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(0,200,0,1)";
// this line is wrong should be ctx.fillRect(x, 20, 50, 50). It is correct in the video
ctx.fillRect = (x, 20, 50, 50);//drawing the rectangle
// restore not needed
//ctx.restore();
x += 5; //increment the x position by some numeric value
// creating a string for a timer is bad. It invokes the parser and is slooowwwwww...
// For animations you should avoid setTimeout altogether and use
// requestAnimationFrame
// var loopTimer = setTimeout('draw('+x+','+y+')', 2000);
requestAnimationFrame(draw);
// you were missing the closing curly.
}
</script>
There is lots more wrong with the tut. It can be excused due to it being near 5 years old. You should look for more up todate tutorials as 5 years is forever in computer technology.
Here is how to do it correctly.
// This script should be at the bottom of the page just befor the closing body tag
// If not you need to use the onload event to start the script.
// define a function that starts the animation
function startAnimation() {
animating = true; // flag we are now animating
x = 10;
y = 10;
// animation will start at next frame or restart at next frame if already running
}
// define the animation function
function anim() {
if (animating) { // only draw if animating
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red"; //coloring the rectangle
ctx.fillRect(x, y, 50, 50); //drawing the rectangle
x += xSpeed;
}
// set animation timer for next frame
requestAnimationFrame(anim);
}
// add a click listener to the start button. It calls the supplied function every time you click the button
startAnimButton.addEventListener("click", startAnimation);
const ctx = canvas.getContext('2d'); // get the 2d rendering context
// set up global variables to do the animation
var x, y, animating;
animating = false; // flag we are not animating
const xSpeed = 50 / 60; // Speed is 50 pixels per second at 60fps
// dont slow the animation down via frame rate
// slow it down by reducing speed.
// You only slow frame rate if the machine
// can not handle the load.
// start the animation loop
requestAnimationFrame(anim);
canvas {
border: 1px solid #666;
}
<!-- don't add events inline -->
<button id="startAnimButton">Draw</button>
<canvas id="canvas" width="512" height="128"></canvas>

Buggy canvas animation on click with JavaScript

I'm trying to run a simple animation each time when user clicks on canvas. I'm sure I did something wrong as the animation doesn't even fire at times. I have never used canvas animation before and have difficulty understanding how it should be constructed within a for loop.
fgCanvas.on('mousedown', function(e) {
var cX = Math.round((e.offsetX - m) / gS),
cY = Math.round((e.offsetY - m) / gS);
clickDot({x:cX,y:cY});
});
function clickDot(data) {
for (var i = 1; i < 100; i++) {
fctx.clearRect(0, 0, pW, pH);
fctx.beginPath();
fctx.arc(data.x * gS + m, data.y * gS + m, i/10, 0, Math.PI * 2);
fctx.strokeStyle = 'rgba(255,255,255,' + i/10 + ')';
fctx.stroke();
}
requestAnimationFrame(clickDot);
}
Full code is here: http://jsfiddle.net/3Nk4A/
The other question is how can slow down the animation or add some easing, so the rings are drawn slower towards the end when they disappear?
You can use requestAnimationFrame plus easing functions to create your desired effect:
A Demo: http://jsfiddle.net/m1erickson/cevGf/
requestAnimationFrame creates an animation loop by itself--so there's no need to use a for-loop inside requestAnimationFrame's animation loop.
In its simplest form, this requestAnimationFrame loop will animate your circle:
var counter=1;
animate();
function animate(){
// stop the animation after it has run 100 times
if(counter>100){return;}
// there's more animating to do, so request another loop
requestAnimationFrame(animate);
// calc the circle radius
var radius=counter/10;
// draw your circle
}
To get the animation to speed-up or slow-down, you can use easings. Easings change a value (like your radius) over time, but they change that value unevenly. Easings speed-up and slow-down over the duration of the animation.
Robert Penner made a great set of easing algorithms. Dan Rogers coded them in javascript:
https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
You can see working examples of his easing functions here:
http://easings.net/
Here's annotated code using requestAnimationFrame plus easings to animate your circles.
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
// set the context styles
ctx.lineWidth=1;
ctx.strokeStyle="gold";
ctx.fillStyle="#888";
// variables used to draw & animate the ring
var PI2=Math.PI*2;
var ringX,ringY,ringRadius,ingCounter,ringCounterVelocity;
// fill the canvas with a background color
ctx.fillRect(0,0,canvas.width,canvas.height);
// tell handleMouseDown to handle all mousedown events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
// set the ring variables and start the animation
function ring(x,y){
ringX=x;
ringY=y;
ringRadius=0;
ringCounter=0;
ringCounterVelocity=4;
requestAnimationFrame(animate);
}
// the animation loop
function animate(){
// return if the animation is complete
if(ringCounter>200){return;}
// otherwise request another animation loop
requestAnimationFrame(animate);
// ringCounter<100 means the ring is expanding
// ringCounter>=100 means the ring is shrinking
if(ringCounter<100){
// expand the ring using easeInCubic easing
ringRadius=easeInCubic(ringCounter,0,15,100);
}else{
// shrink the ring using easeOutCubic easing
ringRadius=easeOutCubic(ringCounter-100,15,-15,100);
}
// draw the ring at the radius set using the easing functions
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.arc(ringX,ringY,ringRadius,0,PI2);
ctx.closePath();
ctx.stroke();
// increment the ringCounter for the next loop
ringCounter+=ringCounterVelocity;
}
// Robert Penner's easing functions coded by Dan Rogers
//
// https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
//
// now=elapsed time,
// startValue=value at start of easing,
// deltaValue=amount the value will change during the easing,
// duration=total time for easing
function easeInCubic(now, startValue, deltaValue, duration) {
return deltaValue*(now/=duration)*now*now + startValue;
}
function easeOutCubic(now, startValue, deltaValue, duration) {
return deltaValue*((now=now/duration-1)*now*now + 1) + startValue;
}
// handle mousedown events
function handleMouseDown(e){
// tell the browser we'll handle this event
e.preventDefault();
e.stopPropagation();
// calc the mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// animate a ring at the mouse position
ring(mouseX,mouseY);
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Click in the canvas to draw animated circle with easings.</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

jQuery or Canvas Screensaver for website with timeout on site back to screensaver page

I have a multipage site that the main home page simply has a single image logo on it. When this page is visited I need the image to move around like a screensaver. When any touch is registered the page will go to a second page. That second page has other data and info but nothing is touched for ten minutes it will default and go back to the entry page and the screensaver.
So, two things.
One, move an image around like a screensaver using jQuery.
Two, the other page has a timeout of ten minutes if no touch is registered that knocks the person back to the first page that has the screensaver.
This is an HTML5 page so if jQuery will not work, something using HTML5 and Canvas might.
This is for a site that will be used as a kiosk and a touch screen.
On the screensaver page:
You can use an html canvas that floats your logo image around the screen. Here's a link:
http://jsfiddle.net/m1erickson/E3Qda/
On the second page:
How about starting a 10 minute setTimeout when the page first loads.
If the user triggers a touch event before 10 minutes, (1) clearTimeout the original timeout (2) setTimeout for a new 10 minutes.
Here's example code for the screensaver page:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
img{border:1px solid purple;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var ball;
var ballX=60;
var ballY=150;
var ballRadius=50;
var image=new Image();
image.onload=function(){
// resize the image to fit inside the ball's radius
var c=document.createElement("canvas");
var cctx=c.getContext("2d");
// calc the max side length of a square that fits the ball
var maxSide=Math.sqrt(2*ballRadius*ballRadius);
// calc the max rect size that fits in the ball
var iw=image.width;
var ih=image.height;
var maxW,maxH;
if(iw>=ih){
maxW=maxSide;
maxH=maxSide*iw/ih;
}else{
maxW=maxSide*ih/iw;
maxH=maxSide;
}
// size the temp canvas to the max rect size
c.width=maxW;
c.height=maxH;
// draw the image to the temp canvas
cctx.drawImage(image,0,0,iw,ih,0,0,maxW,maxH);
var ballimg=new Image();
ballimg.onload=function(){
ball={x:ballX,y:ballY,r:ballRadius,img:ballimg,imgSide:maxSide,directionX:1,directionY:1};
drawBall(ball);
}
ballimg.src=c.toDataURL();
requestAnimationFrame(animate);
}
image.src="ship.png";
function drawBall(ball){
// clip image inside ball
ctx.save();
ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2,false);
ctx.closePath();
ctx.clip();
ctx.fillStyle="white";
ctx.fillRect(ball.x-ball.r,ball.y-ball.r,ball.r*2,ball.r*2);
ctx.drawImage(ball.img, ball.x-ball.imgSide/2,ball.y-ball.imgSide/2);
ctx.restore();
ctx.beginPath();
ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2,false);
ctx.closePath();
ctx.strokeStyle="lightgray";
ctx.lineWidth=2;
ctx.stroke();
ctx.beginPath();
ctx.arc(ball.x,ball.y,ball.r+2,0,Math.PI*2,false);
ctx.closePath();
ctx.strokeStyle="gray";
ctx.lineWidth=2;
ctx.stroke();
}
function animate(time) {
requestAnimationFrame(animate);
// move with collision detection
ball.x+=ball.directionX;
if(ball.x-ball.r<0 || ball.x+ball.r>canvas.width){
ball.directionX*=-1;
ball.x+=ball.directionX;
}
ball.y+=ball.directionY;
if(ball.y-ball.r<0 || ball.y+ball.r>canvas.height){
ball.directionY*=-1;
ball.y+=ball.directionY;
}
// Draw
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBall(ball);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

Painting On A Canvas And Deleting It Automatically (HTML5)

Here's my objective. I want to literally paint on a canvas element then automatically erase it in a quick gradual manner. The similar implementation is somewhat like this: http://mario.ign.com/3D-era/super-mario-sunshine
I want to make it simple. I just simply want to paint and then erase the recently painted stroke. Where do I start? Is there a simple approach on painting on a canvas without using any kind of plugin? I am currently using wPaint.js and it's not really what I want. Is there a way of painting on a canvas and undoing without too much complex code?
Here is how to let the user draw a self-disappearing line:
Create a polyline by saving points to an array when the user drags the mouse.
In an animation loop, clear the screen and redraw that polyline.
But each loop, leave out the earliest points (making the earliest points “disappear”).
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/LT6Ln/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth=15;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isDown=false;
var points=[];
var minPoint=0;
var PI2=Math.PI*2
var radius=20;
var fps = 20;
var lastTime=0;
animate();
function animate() {
setTimeout(function() {
requestAnimFrame(animate);
// draw a polyline using the saved points array
// but start later in the array each animation loop
if(minPoint<points.length){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.beginPath();
ctx.moveTo(points[minPoint].x,points[minPoint.y]);
for(var i=minPoint+1;i<points.length;i++){
var pt=points[i];
ctx.lineTo(pt.x,pt.y);
}
ctx.stroke();
minPoint++;
}
}, 1000 / fps);
}
function handleMouseDown(e){
isDown=true;
}
function handleMouseUp(e){
isDown=false;
}
function handleMouseOut(e){
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// accumulate points for the polyline but throttle
// the capture to prevent clustering of points
if(Date.now()-lastTime>20){
points.push({x:mouseX,y:mouseY});
lastTime=Date.now();
}
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h3>Drag to create a self-clearing line.</h3>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
[ Update: Using complex effects instead of a simple line ]
Sure. You can use a spraypaint effect instead of a line.
However, this effect requires some expensive processing!
The spraypaint effect is often created by drawing multiple random 1x1 pixels around a centerpoint.
Assuming 10 droplets per “spray”, every point along your polyline requires:
10 X fillRect(x,y,1,1) draws on the canvas (instead of 1 X lineTo for the simple line).
20 X Math.random,
10 X Math.cos and
10 X Math.sin.
Here’s an example fiddle of a “spraypaint” effect: http://jsfiddle.net/m1erickson/zJ2ZR/
Keep in mind that all this processing must take place within the small time allowed by requestAnimationFrame (often 16-50 milliseconds per frame).
Doing the expensive spraypaint on each of 20-50 accumulated points along the polyline will likely not fit inside the time of an RAF frame.
To make spraypainting fit inside the time allowed by RAF, you will need to “cache” the effect:
Create 1 random “spray” in a 10px by 10px offscreen canvas.
Create an image from that canvas using canvas.toDataURL.
Add that image to an array.
Repeat step #1 maybe a dozen times (to get a good variety of random spray images)
Then instead of context.lineTo or spraypainting on-the-fly, just do this:
context.drawImage(myCachedSprays[nextSprayIndex],point.x,point.y);
Use Kinetic.js . Its is very easy to learn. By this you can add or remove any painted stroke very easly.
See the working of it from here : http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-batch-draw/

Categories

Resources