Working on a project and cannot seem to get my animation right. I will not be showing the code because it simply doesn't work but it would be cool if someone were to give me a few pointers on how to animate a cloud of smoke moving upwards while slowly fading and increasing in size.
This effect should technically repeat once the y value reaches 0 i.o.w. the cloud reaches the top of the canvas.
What I need to know is how do I animate this, and which methods do I use. This is a kind of a self learning assignment.
Thanks in advance.
Here is a Plunker example of sprites growing in size and fading in transparency.
It is done using Pixi.js which actually renders in webgl with a canvas fallback. It should be possible to take the algorithm and apply it to raw canvas (although it would take some work).
var insertAfter = function(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
var range = function(aCount) {
return new Array(aCount)
}
function main() {
var el_main = document.getElementById("animation_main");
var el_div = document.createElement('div');
el_div.setAttribute('id', 'main_stage');
insertAfter(el_div, el_main);
renderer = PIXI.autoDetectRenderer(300, 300, {
transparent: true,
antialias: true
});
el_div.appendChild(renderer.view);
window.stage = new PIXI.Container();
window.stage.x = 0;
window.stage.y = 0;
renderer.render(window.stage);
var s = [];
for (x of range(400)) {
tCircle = new PIXI.Graphics();
tCircle.beginFill(0x000000, 1);
tCircle.s = (Math.random() * 2) + 1;
tCircle.drawCircle(0, 0, 5 - tCircle.s);
tCircle.x = Math.random() * 300
tCircle.y = (Math.random() * 50) + 20
tCircle.endFill();
s.push(tCircle);
window.stage.addChild(tCircle)
}
window.t = 0
animate = function(t) {
d = t - window.t
window.t = t
//Animation Start
for (n in s){
s[n].x += ((s[n].s / 25) * d)
s[n].alpha = 1 - s[n].x / 300
s[n].scale.x = 1 - s[n].alpha
s[n].scale.y = 1 - s[n].alpha
if (s[n].x > 300) {
s[n].x = 0
s[n].y = (Math.random() * 50) + 20
}
}
renderer.render(window.stage)
//Animation End
requestAnimationFrame(animate);
}
requestAnimationFrame(animate)
}
document.addEventListener("DOMContentLoaded", function(e){
main();
});
At the moment all of the tweening is linear ... it might look more realistic with a logarithmic or exponential tween ... but for simplicity i just left it as linear.
Jakob Jenkov has done a really nice on-line book about canvas here:
http://tutorials.jenkov.com/html5-canvas/index.html
Since yours is a learning experience, I would just point you towards:
The basic workflow of html5 Canvas: Anything drawn on the canvas cannot be altered, so all canvas animation requires repeatedly doing these things in an animation loop: (1) clearing the canvas, (2) calculating a new position for your objects, and (3) redrawing the objects in their new positions.
Animations: requestAnimationFrame as a timing loop: http://blog.teamtreehouse.com/efficient-animations-with-requestanimationframe
Transformations: Canvas gives you the ability to scale, rotate and move the origin of its drawing surface.
Styling: Canvas provides all the essential styling tools for drawing--including globalAlpha which sets opacity.
I am drawing a background image on a canvas element. I create a loop with requestAnimationFrame. In this loop, I draw an image onto the canvas with the appropriate coordinates.
The animation seems to be smooth, in Chrome 60 fps, but I have few glitches every now and then. It's worse in Firefox than in Chrome. It's better when I view it with a clean profile, without open tabs - but it's still not perfect.
Here is the full source: http://jsbin.com/vopiw/1/edit?html,output
This function gets called in every frame:
function draw(delta) {
totalSeconds += delta;
var vx = 100; // the background scrolls with a speed of 100 pixels/sec
var numImages = Math.ceil(canvas.width / img.width) + 1;
var xpos = totalSeconds * vx % img.width;
context.save();
context.translate(-xpos, 0);
for (var i = 0; i < numImages; i++) {
context.drawImage(img, i * img.width, 0);
}
context.restore();
}
Can you spot anything, which could be a real performance drawback?
What I have found so far:
memory consumption is growing slightly but constantly
BUT there is no garbage collection happening, which could be blamed for the glitches
Do you maybe have any clues?
Use the image as a background image on the element itself and use background position to scroll it.
Instead of the img onload just go directly into the code:
(function imageLoaded() {
canvas.style.backgroundImage = 'url(...)';
canvas.style.backgroundRepeat = 'repeat-x';
draw(0);
...
Then just update the draw() method with something like this:
// cache these
var iw = 400,
cw = canvas.width;
function draw(delta) {
totalSeconds += delta;
var vx = 100; // if always 100 just insert the value directly below
var numImages = ((cw / iw)|0) + 1; // use logic OR to remove fractions
var xpos = totalSeconds * vx % iw;
// update background position
canvas.style.backgroundPosition = (-xpos + iw) + 'px 0';
}
The second issue is the way you calculate the time delta. Using the low-resolution timer can add to the jerky-ness.
Try to use the built-in high-resolution timer instead. Luckily the rAF provides a high-res time stamp which you can use instead:
function loop(now) { // use argument from rAF (hi-res timestamp)
if (!looping) {
return;
}
requestAnimationFrame(loop);
var deltaSeconds = (now - lastFrameTime) * 0.001; //mul is faster than div
lastFrameTime = now;
draw(deltaSeconds);
}
Modified jsbin
This hands the drawing action to the browser but have in mind that the gain is not all that. The reason is that the drawImage() method is pretty fast in itself but you are saving a few steps in the JavaScript which is the real bottle-neck (canvas is very fast in itself despite the myth) and the repetition of these draw operations are left to internal compiled code in the browser.
Other factors that influence the smoothness is the hardware clock and hardware capability in general as well as other things going on in the browser.
I would also put that canvas element on an absolute or fixed position as the browser will give the element a separate bitmap (not related to canvas bitmap) for it which could improve the CSS background performance (not shown in the modified jsbin).
I'm trying to use the following effect on a HTML5 game: http://somethinghitme.com/projects/metaballs/
But since its a game (as opposed to graphical demo) I have tighter FPS requirements, I need time to calculate the physics and the some other things and my biggest bottleneck is the code for the metaballs.
The following code is what I got after stripping the original code for performance, its not as pretty but it's enough for my purposes:
ParticleSpawner.prototype.metabilize = function(ctx) {
var imageData = this._tempCtx.getImageData(0,0,900,675),
pix = imageData.data;
this._tempCtx.putImageData(imageData,0,0);
for (var i = 0, n = pix.length; i <n; i += 4) {
if(pix[i+3]<210){
pix[i+3] = 0;
}
}
//ctx.clearRect(0,0,900,675);
//ctx.drawImage(this._tempCanvas,0,0);
ctx.putImageData(imageData, 0, 0);
}
I had another loop on my code and I managed to increase its performance by using the technique described on the following link http://www.fatagnus.com/unrolling-your-loop-for-better-performance-in-javascript/ but using the same on this actually decreases the performance (maybe I did it wrong?)
I also researched web workers to see if I could split the load (since the code runs for each pixel individually) but the example I found on this link http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx actually runs slower when using web workers.
What else can I do? Is there a way to remove the branching from the loop? Another way to unroll it? Or is this the best I can do?
Edit:
This is some of the surrounding code:
ParticleSpawner.prototype.drawParticles = function(ctx) {
this._tempCtx.clearRect(0,0,900,675);
var iterations = Math.floor(this._particles.getNumChildren() / 8);
var leftover = this._particles.getNumChildren() % 8;
var i = 0;
if(leftover > 0) {
do {
this.process(i++);
} while(--leftover > 0);
}
do {
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
} while(--iterations > 0);
this.metabilize(ctx);
}
and the process method:
ParticleSpawner.prototype.process = function(i) {
if(!this._particles.getChildAt(i)) return;
var bx = this._particles.getChildAt(i).x;
var by = this._particles.getChildAt(i).y;
if(bx > 910 || bx < -10 || by > 685) {
this._particles.getChildAt(i).destroy();
return;
}
//this._tempCtx.drawImage(this._level._queue.getResult("particleGradient"),bx-20,by-20);
var grad = this._tempCtx.createRadialGradient(bx,by,1,bx,by,20);
this._tempCtx.beginPath();
var color = this._particles.getChildAt(i).color;
var c = "rgba("+color.r+","+color.g+","+color.b+",";
grad.addColorStop(0, c+'1.0)');
grad.addColorStop(0.6, c+'0.5)');
grad.addColorStop(1, c+'0)');
this._tempCtx.fillStyle = grad;
this._tempCtx.arc(bx, by, 20, 0, Math.PI*2);
this._tempCtx.fill();
};
As can be seen, I tried using images instead of gradient shapes, but the performance was worse, I also tried to use ctx.drawImage instead of putImageData, but it loses the alpha and is not faster. I can't think of an alternative to achieve the desired effect. The current code runs perfectly on Google Chrome, but Safari and Firefox are really slow. Is there anything else I can try? Should I just give up on those browsers?
Updated
Some techniques that can be applied
Here are some optimization techniques that can be applied to make this work more fluent in FF and Safari as well.
That being said: Chrome's canvas implementation is very good and much faster (at the moment) than the bone provided by Firefox and Safari. The new Opera uses the same engine as Chrome and is (about?) equally as fast as Chrome's.
For this to work fine cross-browser some compromises needs to be made and as always quality will suffer.
The techniques I try to demonstrate are:
Cache a single gradient that is used as meta ball basis
Cache everything if possible
Render in half resolution
Use drawImage() to update main canvas
Disable image smoothing
Use integer coordinates and sizes
Use requestAnimationFrame()
Use while loops as often as you can
Bottlenecks
There is a high cost in generating a gradient for each metaball. So when we cache this once and for all we will just by doing that notice a huge improvement in performance.
The other point is getImageData and putImageData and the fact that we need to use a high-level language to iterate over a low-level byte array. Fortunately the array is typed array so that helps a little but we won't be able to get much more out of it unless we sacrifice more quality.
When you need to squeeze everything you can the so-called micro-optimizations becomes vital (these has an undeserved bad reputation IMO).
From the impression of your post: You seem to be very close to have this working but from the provided code I cannot see what went wrong so-to-speak.
In any case - Here is an actual implementation of this (based on the code you refer to):
Fiddle demo
Pre-calculate variables in the initial steps - everything we can pre-calculate helps us later as we can use the value directly:
var ...,
// multiplicator for resolution (see comment below)
factor = 2,
width = 500,
height = 500,
// some dimension pre-calculations
widthF = width / factor,
heightF = height / factor,
// for the pixel alpha
threshold = 210,
thresholdQ = threshold * 0.25,
// for gradient (more for simply setting the resolution)
grad,
dia = 500 / factor,
radius = dia * 0.5,
...
We use a factor here to reduce the actual size and to scale the final render to on-screen canvas. For each 2 factor you save 4x pixels exponentially. I preset this to 2 in the demo and this works great with Chrome and good with Firefox. You might even be able to run factor of 1 (1:1 ratio) in both browsers on a better spec'ed machine than mine (Atom CPU).
Init the sizes of the various canvases:
// set sizes on canvases
canvas.width = width;
canvas.height = height;
// off-screen canvas
tmpCanvas.width = widthF;
tmpCanvas.height = heightF;
// gradient canvas
gCanvas.width = gCanvas.height = dia
Then generate a single instance of a gradient that will be cached for the other balls later. Worth to notice: I initially used only this to draw all the balls but later decided to cache each ball as an image (canvas) instead of drawing and scaling.
This has a memory penalty but increases the performance. If memory is of importance you can skip the caching of rendered balls in the loop that generates them and just drawImage the gradient canvas instead when you need to draw the balls.
Generate gradient:
var grad = gCtx.createRadialGradient(radius, radius, 1, radius, radius, radius);
grad.addColorStop(0, 'rgba(0,0,255,1)');
grad.addColorStop(1, 'rgba(0,0,255,0)');
gCtx.fillStyle = grad;
gCtx.arc(radius, radius, radius, 0, Math.PI * 2);
gCtx.fill();
Then in the loop that generates the various metaballs.
Cache the calculated and rendered metaball:
for (var i = 0; i < 50; i++) {
// all values are rounded to integer values
var x = Math.random() * width | 0,
y = Math.random() * height | 0,
vx = Math.round((Math.random() * 8) - 4),
vy = Math.round((Math.random() * 8) - 4),
size = Math.round((Math.floor(Math.random() * 200) + 200) / factor),
// cache this variant as canvas
c = document.createElement('canvas'),
cc = c.getContext('2d');
// scale and draw the metaball
c.width = c.height = size;
cc.drawImage(gCanvas, 0, 0, size, size);
points.push({
x: x,
y: y,
vx: vx,
vy: vy,
size: size,
maxX: widthF + size,
maxY: heightF + size,
ball: c // here we add the cached ball
});
}
Then we turn off interpolating for images that are being scaled - this gains even more speed.
Note that you can also use CSS in some browsers to do the same as here.
Disable image smoothing:
// disable image smoothing for sake of speed
ctx.webkitImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.oImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false; // future...
Now the non-critical parts are done. The rest of the code utilizes these tweaks to perform better.
The main loop now looks like this:
function animate() {
var len = points.length,
point;
// clear the frame of off-sceen canvas
tmpCtx.clearRect(0, 0, width, height);
while(len--) {
point = points[len];
point.x += point.vx;
point.y += point.vy;
// the checks are now exclusive so only one of them is processed
if (point.x > point.maxX) {
point.x = -point.size;
} else if (point.x < -point.size) {
point.x = point.maxX;
}
if (point.y > point.maxY) {
point.y = -point.size;
} else if (point.y < -point.size) {
point.y = point.maxY;
}
// draw cached ball onto off-screen canvas
tmpCtx.drawImage(point.ball, point.x, point.y, point.size, point.size);
}
// trigger levels
metabalize();
// low-level loop
requestAnimationFrame(animate);
}
Using requestAnimationFrame squeezes a little more of the browser as it is intended to be more low-level and more efficient than just using a setTimeout.
The original code checked for both edges - this is not necessary as a ball can only cross one edge at the time (per axis).
The metabolize function is modified like this:
function metabalize(){
// cache what can be cached
var imageData = tmpCtx.getImageData(0 , 0, widthF, heightF),
pix = imageData.data,
i = pix.length - 1,
p;
// using a while loop here instead of for is beneficial
while(i > 0) {
p = pix[i];
if(p < threshold) {
pix[i] = p * 0.1667; // multiply is faster than div
if(p > thresholdQ){
pix[i] = 0;
}
}
i -= 4;
}
// put back data, clear frame and update scaled
tmpCtx.putImageData(imageData, 0, 0);
ctx.clearRect(0, 0, width, height);
ctx.drawImage(tmpCanvas, 0, 0, width, height);
}
Some micro-optimizations that actually helps in this context.
We cache the pixel value for alpha channel as we use it more than two times. Instead of diving on 6 we multiply with 0.1667 as multiplication is a tad faster.
We have already cached tresholdQ value (25% of threshold). Putting the cached value inside the function would give a little more speed.
Unfortunately as this method is based on the alpha channel we need to clear also the main canvas. This has a (relatively) huge penalty in this context. The optimal would be to be able to use solid colors which you could "blit" directly but I didn't look Into that aspect here.
You could also had put the point data in an array instead of as objects. However, since there are so few this will probably not be worth it in this case.
In conclusion
I have probably missed one or two (or more) places which can be optimized further but you get the idea.
And as you can see the modified code runs several times faster than the original code mainly due to the compromise we make here with quality and some optimizations particularly with the gradient.
There is scope of improvement in programming, in drawing particle section.
instead of using
if(leftover > 0) {
do {
this.process(i++);
} while(--leftover > 0);
}
you can just use this
while(leftover > 0) {
this.process(i++);
leftover --;
}
This will reduce one step of condition checking of if and also the (--)operator that decrements one value and checks. this will reduce the complexity
with all do while you have (--) that can be removed, with simple statement this will reduce the Cyclomatic Complexity of this particular code and make this code faster.
ultimately this will give the performance improvement with the faster processing of your code and less use of CPU and resources. although Ken's answer is also working one, I have created one more fiddle that is similar to your sample site with more speed.
fiddle
If any problem please leave a comment, and update fiddle with game code for performance check.
This loop is already pretty simple, uses stable types that JIT likes, so I don't think you can get significant improvement.
I've eliminated +3 and unrolled it a bit (assuming width*height is divisible by 4). I've added |0 "cast" to integer that makes it sliiightly faster in V8.
Overall it gave 10% improvement:
var i = (3 - 4)|0;
var n = (pix.length - 16)|0;
while(i < n) {
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
}
If you need it to be massively faster, then maybe use lower-resolution canvas for the effect?
I would like to know why this simple ball moving code runs smooth in IE and Chrome
and in Firefox it appears sluggish, although it maintains the same FPS rate.
What would I have to do to achieve the same smooth movement across all browsers?
var canvas,canvasContext,
ball,txt_shadow,txt_fps,
speed,angle;
function init() {
canvas = document.getElementById("canvas");
canvasContext = canvas.getContext("2d");
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
stage = new createjs.Stage(canvas);
stage.autoClear = true;
txt_shadow= new createjs.Shadow("black", 1, 1, 0);
ball = new createjs.Shape();
ball.graphics.beginFill("red").drawCircle(0, 0, 40);
txt_fps = new createjs.Text("", "18px Arial", "white");
txt_fps.shadow=txt_shadow;
txt_fps.x=canvas.width/2;txt_fps.y=100;
stage.addChild(txt_fps,ball);
ball.x=canvas.width/2;ball.y=canvas.height/2;
angle=Math.random()*360;
speed=8;
createjs.Ticker.addListener(window);
createjs.Ticker.setFPS(60);
}
function tick() {
fps=createjs.Ticker.getMeasuredFPS();
txt_fps.text=Math.round(fps);
ball.x += Math.sin(angle*(Math.PI/-180))*speed;
ball.y += Math.cos(angle*(Math.PI/-180))*speed;
if (ball.y<0 || ball.x<0 || ball.x>canvas.width || ball.y>canvas.height) {
angle+=45;
}
stage.update();
}
Canvas text rendering is slow. You are doing yourself a disservice by generating the text within <canvas> as opposed to writing the FPS to a separate element.
But one technique you could use to speed up what already been written is to limit certain computationally-expensive tasks (rendering the FPS) do not run at the same frequency as the most critical tasks (the ball bouncing).
EaselJS does not allow you to designate certain tasks run more frequently. (createjs.Ticker.setFPS is a static function.) So we'll need to create a work-around.
Here's a closure that will return true once every sixty times it is called.
var onceEverySixty = (function(){
var i = 0;
return function(){
i++;
return ((i % 60) == 0);
}
})();
Using this closure, we can then implement it in your code to limit how many times the FPS text gets updated:
function tick(){
if(onceEverySixty()){
fps=createjs.Ticker.getMeasuredFPS();
txt_fps.text=Math.round(fps);
}
// The ball-drawing code here, outside the conditional
}
I'm making a small platform game with the canvas element and I'm having trouble working out the best way to use sprites.
Canvas doesn't support animated GIFs, so I can't do that, so instead I made a film strip for my animated sprites to give the illusion that a character is moving. Like this: http://i.imgur.com/wDX5R.png
Here's the relevant code:
function player() {
this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frameCount = 12;
this.runningSprite = new Image();
this.runningSprite.src = "/game/images/runningSprite.png";
this.runningSprite.frameWidth = 34;
this.update = function() {
}
var frameCount = 1;
this.draw = function() {
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
if(frameCount < this.idleSprite.frameCount - 1) { frameCount++; } else { frameCount = 0; }
}
}
var player = new player();
As you can see, I'm defining the idle sprite and also its frame width and frame count. Then in my draw method, I'm using those properties to tell the drawImage method what frame to draw. Everything works fine, but I'm unhappy with the frameCount variable defined before the draw method. It seems... hacky and ugly. Would there be a way that people know of to achieve the same effect without keeping track of the frames outside the draw method? Or even a better alternative to drawing animated sprites to canvas would be good.
Thanks.
You could select the frame depending on some fraction of the current time, e.g.
this.draw = function() {
var fc = this.idleSprite.frameCount;
var currentFrame = 0 | (((new Date()).getTime()) * (fc/1000)) % fc;
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * currentFrame, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
}
That will give you animation with a period of one second (the 1000 is a millisecond value). Depending on your frame rate and animation this might look jerky, but it doesn't rely on persistent counters.