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
<div id="container">
    <canvas id="pixie"></canvas>
    <div id="mountains"></div>
    <div id="grass"></div>
</div>

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).

46 Responses to “HTML5 Canvas Particle Animation”

  1. kyle 22. Feb, 2011 at 4:47 pm #

    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?

    • Tim 23. Feb, 2011 at 1:37 pm #

      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!

  2. Arthwarr 11. Apr, 2011 at 5:08 am #

    This is absolutely fantastic.

    Thanks a lot for writing this piece of code, i couldnt understand how it worked on apple website

    • Tim 11. Apr, 2011 at 7:21 am #

      No problem. Thanks for reading!

  3. John 18. May, 2011 at 12:14 pm #

    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

    • Tim 18. May, 2011 at 1:07 pm #

      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.

  4. Tobias 17. Aug, 2011 at 10:45 am #

    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

    • Tim 18. Aug, 2011 at 8:30 am #

      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.

  5. Shannon 12. Sep, 2011 at 7:04 pm #

    Sorry For Being A Total Newb! :P

    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 :D

    • Shannon 12. Sep, 2011 at 7:06 pm #

      & In This Case Is Their Like Some CSS You Need To Do This?

    • Tim 14. Sep, 2011 at 3:26 pm #

      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

      to the #container and give it the following CSS rules:

      #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!

  6. dhaval 18. Dec, 2011 at 12:53 am #

    how can I add text on the top?

  7. Ashley 04. Jan, 2012 at 9:05 am #

    This is absolutely fantastic. You should be proud.

    Well done!

  8. Kelli 17. Jan, 2012 at 8:49 pm #

    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!

    • Tim 17. Jan, 2012 at 9:54 pm #

      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:

      1
      2
      3
      4
      5
      6
      window.onresize = function(e){
          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!

    • Kelli 18. Jan, 2012 at 2:31 am #

      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 :)

    • Tim 18. Jan, 2012 at 2:46 am #

      That’s great! Seems like you’re doing fine to me :0)

    • Kelli 18. Jan, 2012 at 2:38 am #

      oops….. scratch that

      setInterval(draw,rint);

      at the end there. Don’t need to run it twice unless i ant crazy fast stars haha.

  9. Valentines Day Gifts 30. Jan, 2012 at 1:41 am #

    A video game that’s realistic? All I? can think of are driving games.

  10. Anton Korzhuk 16. Mar, 2012 at 10:07 am #

    super slick effect, thanks for sharing

  11. Jordan Thomas 17. Apr, 2012 at 11:17 pm #

    How would you go about putting characters/letters in place of particles? Also, how CPU intensive is this?

    • Tim 25. Apr, 2012 at 3:33 pm #

      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.

  12. Kirk Caito 01. May, 2012 at 6:08 pm #

    Hello there, I really wished to tell you that I really bookmarked this article since everyone need to read this. Genuinely wonderful, thank you.

    • Tim 15. May, 2012 at 3:27 pm #

      Thanks!

  13. Vladyslav 03. May, 2012 at 7:19 am #

    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

  14. Jackson Isted 13. May, 2012 at 8:49 pm #

    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?

  15. oscar 12. Jun, 2012 at 11:18 am #

    This is such a great tutorial I was looking for this type of effects everywhere… Great Job!

  16. Sean 12. Jul, 2012 at 6:23 pm #

    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!

    • Sean 02. Aug, 2012 at 4:20 pm #

      is choppy in IE 9 and want to smooth out the particle animation on the screen. is it possible?

  17. Kyle Wistrand 02. Aug, 2012 at 9:28 pm #

    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 :)

  18. Lars 24. Aug, 2012 at 10:52 am #

    Thanks very beautiful and useful!

    • Tim 24. Aug, 2012 at 10:57 am #

      You’re too kind! Thanks for reading!

  19. Rita Vyas 17. Sep, 2012 at 11:08 pm #

    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?

  20. Chauffagiste 78 13. Nov, 2012 at 5:17 am #

    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.

  21. Tom 12. Jan, 2013 at 12:49 am #

    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 :)

  22. Lamar Anene 02. Mar, 2013 at 7:08 am #

    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.

  23. www.foreign.go.tz 10. Mar, 2013 at 10:49 am #

    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

  24. Hugh 13. Apr, 2013 at 9:19 pm #

    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

    • Tim 18. Apr, 2013 at 8:41 am #

      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!

  25. Barry Schulman 19. Apr, 2013 at 1:18 am #

    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

Trackbacks/Pingbacks

  1. How to make a Javascript Particle Engine | Flyers Blog - 14. Nov, 2011

    [...] de particule basé sur l’animation de simples éléments DIVs. Vous pouvez voir un exemple de moteur de particule en HTML 5 pour approfondir le sujet mais ce n’est pas l’orientation du moteur que je vais vous [...]

  2. HorribleDevs | 10 Kick Ass jQuery Plugins for your Next Project - 22. Mar, 2013

    [...] Tutorial | Demo [...]

  3. 10 Awesome jQuery Plugins & Tutorials You Can’t Ignore - 29. Mar, 2013

    [...] Tutorial | Demo [...]

  4. 10 Essential jQuery Tutorials for Programmer - 03. Apr, 2013

    [...] Demo | Tutorial [...]

Leave a Reply