HTML5 Canvas Particle Animation
19 Jan
UPDATE: yes, I know some people out there will see some flickering when viewing the demo. This is because a) I didn’t implement a double buffer, and b) there is no built-in canvas support for double buffering. There is a very simple solution, but I’ll save that for another time.
Having never really been a user of Apple products, I guess it’s not all that surprising that I’ve never been to the MobileMe website. However, after Googling something like the lines of “most popular e-mail providers,” I came across its login page and was blown away.
Holy moly, that looks amazing. Not only is the design great and the colors are just about perfect, but those little sparkles are crazy! They move in what appears to be 3D space, pulsing from behind the cloud to further into the background amongst and into (while highlighting) the iPads and iPhones, and follow your mouse movements for extra fun.
After poking around the source and giving up on 100% comprehension, I decided it might be fun to take a whack at my own thoroughly underwhelming homage to the MobileMe page.
For some reason or another, I decided to make a night scene of some mountains and grass for some stars to streak across (I also threw in some parallax, but that’s for another post). Here’s what I’m starting out with prior to any canvas magic:
And here’s the accompanying code to make that happen (it’s pretty simple, but I’m including it anyways so as little is left out as possible):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #container { overflow:hidden; position:relative; } #pixie { z-index:0; background:-moz-linear-gradient(top, #040429, #257eb7); background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#040429), color-stop(100%,#257eb7)); } #mountains, #grass { width:100%; position:absolute; bottom:0; } #mountains { height:156px; z-index:1; background:url(mountains.png) repeat-x 0 0; } #grass { height:62px; z-index:2; background:url(grass.png) repeat-x left 10px; } |
1 2 3 4 5 |
But that’s child’s play compared to what you really came here for: HTML5 canvas! (You should note, though, that the actual height and width attributes of the canvas element must be set in the HTML or dynamically through JavaScript and not just the CSS, otherwise you’ll get something…weird)
Circle Object
What first needs to happen is we need to create a particle object in JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | function Circle() { this.settings = {time_to_live:8000, x_maxspeed:5, y_maxspeed:2, radius_max:10, rt:1, x_origin:960, y_origin:540, random:true, blink:true}; this.reset = function() { this.x = (this.settings.random ? WIDTH*Math.random() : this.settings.x_origin); this.y = (this.settings.random ? HEIGHT*Math.random() : this.settings.y_origin); this.r = ((this.settings.radius_max-1)*Math.random()) + 1; this.dx = (Math.random()*this.settings.x_maxspeed) * (Math.random() < .5 ? -1 : 1); this.dy = (Math.random()*this.settings.y_maxspeed) * (Math.random() < .5 ? -1 : 1); this.hl = (this.settings.time_to_live/rint)*(this.r/this.settings.radius_max); this.rt = Math.random()*this.hl; this.settings.rt = Math.random()+1; this.stop = Math.random()*.2+.4; this.settings.xdrift *= Math.random() * (Math.random() < .5 ? -1 : 1); this.settings.ydrift *= Math.random() * (Math.random() < .5 ? -1 : 1); } this.fade = function() { this.rt += this.settings.rt; } this.draw = function() { if(this.settings.blink && (this.rt <= 0 || this.rt >= this.hl)) this.settings.rt = this.settings.rt*-1; else if(this.rt >= this.hl) this.reset(); var new_opacity = 1-(this.rt/this.hl); con.beginPath(); con.arc(this.x,this.y,this.r,0,Math.PI*2,true); con.closePath(); g = con.createRadialGradient(this.x,this.y,0,this.x,this.y,this.r*new_opacity); g.addColorStop(0.0, 'rgba(255,255,255,'+new_opacity+')'); g.addColorStop(this.stop, 'rgba(77,101,181,'+(new_opacity*.6)+')'); g.addColorStop(1.0, 'rgba(77,101,181,0)'); con.fillStyle = g; con.fill(); } this.move = function() { this.x += (this.rt/this.hl)*this.dx; this.y += (this.rt/this.hl)*this.dy; if(this.x > WIDTH || this.x < 0) this.dx *= -1; if(this.y > HEIGHT || this.y < 0) this.dy *= -1; } } |
The first line is some settings for each particle, each of which is named in a pretty self-explanatory manner.
- time_to_live – used to calculate hl–or the half-life–of each particle
- x_maxspeed and y_maxspeed – defines the maximum number of pixels a particle can move each frame
- radius_max – maximum radius a particle can achieve
- rt – used in conjunction with hl to determine how the ratio of maximum speed and full opacity of each particle in each frame
The reset() function just sets up the particle in a new location if it’s the first iteration or if random is set to true and the move() function just moves the particle according to the settings (you’ll notice I have them moving faster as they get smaller and more transparent). The good stuff comes in the draw() function.
Within draw(), you see that a new path is started for each particle and drawn into a circle with the arc() function. Normally you would use this just to draw an arc, but by supplying 2Π, or the radian equivalent to a full circle, as the fifth argument, you get a circle.
Then you can call createRadialGradient() to fill in the circles we just created with color, which I have set with three color stops at various opacities to really make the particles look like they’re glowing. You should note, though, that the con and g variables are set outside of the Circle object and treated as member variables.
Animating the Particles
Finally, you need to create all of the particles and iterate through each one to move and render:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $(document).ready(function(){ WIDTH = 1680; HEIGHT = 1050; canvas = document.getElementById('pixie'); con = canvas.getContext('2d'); for(var i = 0; i < 100; i++) { pxs[i] = new Circle(); pxs[i].reset(); } setInterval(draw,rint); }); function draw() { con.clearRect(0,0,WIDTH,HEIGHT); for(var i = 0; i < pxs.length; i++) { pxs[i].fade(); pxs[i].move(); pxs[i].draw(); } } |
The first part creates each new particle (or Circle() in this case) and stores it in an array. We then set a function to run at a certain interval based on what frames per second we desire to run at.
The draw() function simply iterates over the array and calls each necessary function of each particle to animate its movement. It’s necessary, though, to point out the clearRect() call. Without it, you wouldn’t get an animation of moving stars in the night sky but rather streaking purple circles.
With any luck, you should get something like this:
This is a very base attempt at particle animation with HTML5′s canvas element. You can do a lot of other, crazier things with canvas, but this is a good place to start understanding how it works and how to get animated things moving with JavaScript.
You can also click and drag left and right on the screen to get some parallax going between the mountains and the grass. I’ll go into detail on how to achieve that effect later on in life at some point maybe perhaps.
View the demo (requires you to be running an HTML5-capable browser).




Excellent! So how would you control where the particles originate from and where they burn out? Similar to what Mobile Me did? The particles originate behind the logo and radiate outward. Any idea?
Thanks! It’s pretty simple to get them all to originate from one point (or a region, if you would prefer). In fact, I have the random bool value in the this.s (settings) of the Circle object so you can switch that to false and every point will radiate from the (xdef, ydef) coordinate.
I also have the blink bool value to indicate whether they continue to drift around and fade in again after they fade all the way out or, if set to false, will reset to the origin.
Like I said, pretty simple to get that to happen! However, you’ll notice in the MobileMe site that the particles have random amounts of drift in both the x and y directions so they don’t move in straight lines and have “target tendencies,” meaning they all aim for a particular iDevice in the background. Getting that to happen is a lot more more and easily triples the amount of code necessary to get that working. If you want to know more, though, let me know and I’ll write up something about that, too. Thanks again!
This is absolutely fantastic.
Thanks a lot for writing this piece of code, i couldnt understand how it worked on apple website
No problem. Thanks for reading!
Wow, that looks amazing! I’ve been thinking about creating my own snazzy website in a similar vein
am I allowed to use your code or night scene image or is this only intended as an example?
Thanks,
J
Of course you’re allowed! Feel free to use it all if you so desire, I’m just glad you like it. A little head nod my way when you’re done would be great. I’d love to see what you create.
Thank you so much for this awesome script. I am trying to implement it on my website but just found out that it does not work in Internet Explorer, is there some kind of work around?
Thanks,
Tobias
In general, there is an error that returns in just about every browser. It’s an INDEX_SIZE_ERR (DOM Exception 1) which means that somewhere in the code, there is a negative value being put into a canvas method. In this case, on line 110, there are occasions when the circle’s radius will go into negative territory.
In Chrome and whatnot, these errors are tolerated and the animation will continue. In IE, however, the whole thing just halts. The fix is so ridiculously simple and rectifies both the DOM errors and the flashing/stuttering some users have noticed that I don’t know why I never got around to it before. Replace line 110 with the following two lines:
var cr = this.r * newo;
g = con.createRadialGradient(this.x,this.y,0,this.x,this.y,(cr <= 0 ? 1 : cr));
If you’re talking about an IE version that’s less than 9, though, then that’s because those versions don’t support the canvas element.
And if you’re talking about the missing background gradient, that’s because I didn’t put in those stupid trashy DX filters. I was hoping instead that IE9 would eventually be patched up to IE10 and use some sort of vendor prefix for CSS3 linear gradients.
Sorry For Being A Total Newb!
But In What Part Of The HTML File,
Do I Stick My Website Content Into?
Because Everytime I Try To Stick It Into A Different Div Tag, It Either Shows At The Bottom Or Top Of The Page.
Thanks
& In This Case Is Their Like Some CSS You Need To Do This?
I guess that’s because there really isn’t a place to do so. This wasn’t really made to be anything to house content, but that doesn’t mean you can’t!
Just add another
#content {
position:absolute;
width:50%;
height:50%;
top:25%;
left:25;
border:1px solid #FFF;
}
That should get you started with a white-bordered box in the center of the screen that is half the width and height of the window. You’ll have to do more stuff if you want some scrolling content, though.
Good luck, and thanks for reading!
how can I add text on the top?
This is absolutely fantastic. You should be proud.
Well done!
I’ve done some benchmarking about Canvas animation on Mobile platform and it was clear that Canvas tecnology was really too heavy to be deployed on mobiles platform.
That’s why i’ve made a particle engine based on DIV animation who is really more speedy. Check it out http://www.flyers-web.org/blog/programmation/how-to-make-a-javascript-particle-engine-238
I noticed when you make the window larger, you are left with white space. Is there a way to make it all expand with the window?
Awesome work btw!
Thanks! And there absolutely is a way!
Unfortunately, it’s not as simple as setting both the container <div> and the <canvas> element with width=”100%” and height=”100%”. You can go ahead and do that with the <div>, but <canvas> requires hard-set widths and heights to draw anything (in Chrome anyways).
What you need to do is add an event listener for window.onresize and set it to something like:
2
3
4
5
6
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
$('#canvas').attr('width',WIDTH).attr('height',HEIGHT);
$('#container').width(WIDTH).height(HEIGHT);
};
Although I think in my code, I actually set the ID for the <canvas> element to “#pixie.” But either way, just set an event listener for window.onresize so you can set the appropriate width and heights for the container, <canvas>, and the application variables (in this case, WIDTH and HEIGHT). Thanks again for reading!
Awesome! Thanks. Still learning javascript
. I tweaked it a little since it wasn’t redrawing the stars and came up with this:
window.onresize = function(e){
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
canvas = document.getElementById(‘pixie’);
$(canvas).attr(‘width’, WIDTH).attr(‘height’,HEIGHT);
$(‘#container’).width(WIDTH).height(HEIGHT);
for(var i = 0; i < 100; i++) {
pxs[i] = new Circle();
pxs[i].reset();
}
setInterval(draw,rint);
};
Works perfect for me now
That’s great! Seems like you’re doing fine to me :0)
oops….. scratch that
setInterval(draw,rint);
at the end there. Don’t need to run it twice unless i ant crazy fast stars haha.
A video game that’s realistic? All I? can think of are driving games.
super slick effect, thanks for sharing
How would you go about putting characters/letters in place of particles? Also, how CPU intensive is this?
You can put text at specific coordinates (with font and color set beforehand) with:
context.font = ’12pt Helvetica’;
context.fillStyle = ‘#0000FF’;
context.fillText(‘Your text here’, x, y);
It’s pretty easy. And this shouldn’t be that CPU-intensive. In fact, some browsers already support hardware acceleration, so you may not even have to worry about the CPU.
Hello there, I really wished to tell you that I really bookmarked this article since everyone need to read this. Genuinely wonderful, thank you.
Thanks!
Did somebody have saved https://auth.me.com/my/loginForm/en-us/2E11/javascript-packed.js file ?
guys i someone does – please share it.
I’ll be very grateful to
Is it possible for this to work on iPad’s? I am creating a signup form an would like this animation ontop of a background image but behind a popup (the form) and would like it to move with the form and change width when the screen rotates. Any of this possible?
Definitely possible! According to http://caniuse.com/canvas iOS versions of Safari should be able to work with canvas elements.
As for the rotation, you just need to alter the drawing range with the onorientationchange event, but you should be aware of the order of events as per http://stackoverflow.com/questions/10230064/ipad-canvas-rotation
This is such a great tutorial I was looking for this type of effects everywhere… Great Job!
Hey this is awesome. Any way that I can smooth the animation? I cant figure out what line of code controls the frame rate. thanks!
is choppy in IE 9 and want to smooth out the particle animation on the screen. is it possible?
Hey, is there a way to make the particles come out of two points in the canvas? I’ve tried many things but haven’t been able to figure it out.
Thanks
Thanks very beautiful and useful!
You’re too kind! Thanks for reading!
I implemented this on my site that is http://dev.mvixusa.com but when i run on fire fox my fire fox crashed. Can you please help me for this issue?
It’s really a cool and helpful piece of information. I am glad that you just shared this helpful info with us. Please keep us informed like this. Thanks for sharing.
Great work! Just curious if there is a way to mask the canvas? Instead of having the particles hit the box have them fade out when reaching the edge of the canvas.
Also, is there a way to limit the number of particles displaying. Maybe set max-particle or something?
Thanks for the help
I believe this internet site has got some rattling good info for everyone. “The individual will always be a minority. If a man is in a minority of one, we lock him up.” by Oliver Wendell Holmes.
Unquestionably believe that which you stated. Your favorite justification seemed to
be on the internet the simplest thing to be aware of. I say to you, I definitely get annoyed
while people consider worries that they plainly
don’t know about. You managed to hit the nail upon the top as well as defined out the whole thing without having side effect , people can take a signal. Will likely be back to get more. Thanks
my website … http://www.foreign.go.tz
Hi Tim, this is a really great article and it helped me out a lot in my design. Just wanted to thank you and let you know I’ve linked my site http://www.hughanderson.com to yours. I made a few adaptations and I would enjoy it if you would take a look at my site to see what I’ve done. I ended up using window.RequestAnimationFrame for the animation, and adapted the Pixie to make a Sun. I’m also using Div animation for the clouds on my site. Thanks again for posting this useful tutorial. -Hugh
Wow, thanks for this! Your site looks great and I couldn’t be happier with how my stuff helped you out. And I probably should update this to use RequestAnimationFrame, but that wasn’t how things worked way back in the old 2011. Oh well. Thanks again!
Hi,
I am trying to get this to work on a wordpress site as the background. Problem I face is this:
Either it just covers the page completely
or
it works just in the header section.
this is the site:
http://www.ringarose.co.za
Can you perhaps point me in the right direction?
WOuld be so greatly appreciated