Animating HTML canvas elements
-
27-10-2019 - |
Pergunta
I am trying to animate elements on the <canvas>
but setTimeout and setInterval are giving me problems. My data is in an Array. I get its length. For each node I generate an X and a Y, a cardinal direction and a color. Then my drawing function draws a circle for each one of those nodes.
My problems arise when I try and loop with a setInterval. My thinking was that I could establish all the X, Y values, then loop back and increment them each 10ms-100ms. But every time I use setInterval my child Arrays that hold the data for each circle get a length of 3 and the values are set to undefined.
I coded this once using various objects but couldn't get the animation to work. I learned about the issues with scoping setInterval() in objects. I recoded it without establishing a bunch of objects. still no luck.
$(document).ready(function(){
getTweets();
});
CTX = $('#tweets')[0].getContext("2d");
WIDTH = $('#tweets').width();
HEIGHT = $('#tweets').height();
RADIUS = 10;
TWEETS = [];
CORDS = [];
DIRECTION = ['north','east','south','west'];
function getTweets(){
$.getJSON("http://search.twitter.com/search.json?callback=?&q=scion&rpp=100",
function(r){
var numberOfTweets = r.results.length;
while(numberOfTweets--){
TWEETS.push(r.results[numberOfTweets].text);
}
plotXY();
animateTweets()
});
}
function animateTweets(){
return setTimeout(plotXY(true),100);
}
function plotXY(animating){
if(!animating){
var numberOfTweets = TWEETS.length;
while(numberOfTweets--){
var topY = Math.ceil(Math.random()*(HEIGHT-20)),
leftX = Math.ceil(Math.random()*(WIDTH-20)),
cardinal = Math.floor(Math.random()*4),
color = '#'+Math.floor(Math.random()*16777215).toString(16);
var valCords = validateCords(leftX, topY);
CORDS.push([valCords[0], valCords[1], cardinal, color]);
}
// console.log('animating false');
// console.log(CORDS);
}
else{
var numberOfTweets = TWEETS.length;
while(numberOfTweets--){
if(CORDS[numberOfTweets][2]=='north'){ //NORTH
CORDS[numberOfTweets][1]+=2;
CORDS[numberOfTweets][0]+=2;
}
if(CORDS[numberOfTweets][2]=='east'){ //EAST
CORDS[numberOfTweets][1]+=2;
CORDS[numberOfTweets][0]-=2;
}
if(CORDS[numberOfTweets][2]=='south'){ //SOUTH
CORDS[numberOfTweets][1]-=2;
CORDS[numberOfTweets][0]-=2;
}
if(CORDS[numberOfTweets][2]=='west'){ //WEST
CORDS[numberOfTweets][1]-=2;
CORDS[numberOfTweets][0]+=2;
}
var valCords = validateCords(leftX, topY, numberOfTweets);
CORDS.push([valCords[0],valCords[1],cardinal]);
CORDS.shift();
}
// console.log('animating true');
// console.log(CORDS);
}
drawCircles();
}
function drawCircles(){
console.log('drawing');
var numOfCords = CORDS.length;
clear();
while(numOfCords--){
CTX.fillStyle = CORDS[numOfCords][3];
CTX.beginPath();
CTX.arc(CORDS[numOfCords][0], CORDS[numOfCords][1], RADIUS, 0, Math.PI*2, true);
CTX.closePath();
CTX.fill();
}
}
Solução
I don't know if this is the only problem in your code, but one significant problem is the way you are using setTimeout()
:
setTimeout(plotXY(true), 100); // doesn't work
That says to call the plotXY()
function and then pass whatever it returns as the first parameter to setTimeout
.
Instead, the first parameter to setTimeout()
should be a function expression or reference to a function like this:
setTimeout(plotXY, 100); // works, but doesn't pass parameter to plotXY()
Note that plotXY
does not have parentheses for this purpose. But of course that leaves you with another problem: you want plotXY()
to be called with the parameter true
. That is easily solved by wrapping the call to plotXY()
in another function and passing that other function to setTimeout()
:
setTimeout(function(){ plotXY(true); }, 100); // works
That creates an anonymous function and passes that function to setTimeout()
.
You probably want to call setTimeout()
again at the end of your plotXY()
function, or use setInterval()
, otherwise your animation will only have one step. Or you can do something like the following, which shows both another way to solve the parameter passing problem and a way to keep calling the function with setTimeout()
:
function plotXYProxy() {
plotXY(true);
setTimeout(plotXYProxy, 100);
}
plotXYProxy();
Of course you can add some conditional processing within plotXYProxy()
to decide whether to set each timeout, e.g., you could have plotXY()
return a boolean as to whether the animation has finished and call setTimeout()
or not based on that boolean. Or whatever suits.
Everything I just said about the parameters of setTimeout()
also applies to setInterval()
.