JavaScript Particle Systems part 2: adding some life
The frame below shows a demonstration of the particle system we are about to build. Move your mouse through it to have some fun. If it doesn't work for you, click this link to open the same demo in a popup window.
Introduction
A few months ago I wrote an article that demonstrated how to create a very simple particle system in less than 50 lines of JavaScript code. That article has slowly been finding its way into the Google search indexes and by now it's becoming obvious that I'm not the only one who thinks particle systems deserve more attention and JavaScript is the perfect language to experiment with them. Back then I was quite pleased with being able to implement a fully functional particle system in less than 50 lines of (quite readable) JavaScript code. But I think we can agree that the particle system was not very exciting. Although the system contained all three required ingredients, the end result was a bit "static". From games and other places where particle systems are used, we are used to them being a lot more "alive". So that's the goal for this article: to add some life to our previous particle system, while still keeping it small. In this case I've increased my self-imposed limit to a 100 lines. A generous doubling from our previous system, so we'd better have some real interesting "life" to show for the increase. We'll add three behaviors to the particle sytem.- Gravity
- Grow then shrink
- Mouse control
Gravity
In our first particle system, the particles would fall at a constant speed. The fall speed of each particle was determined at random when creating the particle.dy: Math.random() + 0.5And then we just updated the y position every time:
particles[i].y = particles[i].y + particles[i].dy;I always find it very interesting to add some gravity-like behavior to systems I create. So how about making the particles fall at an increasing speed?
p = particles[i]; p.y += p.dy; p.dy += 0.1; // simulate gravitySo we increase the particle's y velocity every time we update its position. I realize that to make this work like real gravity I'd have to know the particle's mass and do a multiplication. But for this simple example just increasing the dy is already convincing enough. Since the particle will now drop at an ever increasing speed, it is nice to make it go up a bit at first:
dy: -1 + Math.random()So the particles will first move up a bit and then start falling down at an increasing speed. If all goes well, they'll move in a nice arc-like pattern. Again there is no real math or physics reason for these values, these just looked best to me. If you feel the need to make the system more physically correct, just download the source and go ahead. Let me know if you find an interesting combination.
Grow then shrink
Another thing I wasn't too happy with in the original particle system is the static size that the particles have. Each particle is created to be 3px in width and height. By the way: there was a bug in the original code such that the sizing didn't work in Internet Explorer. As my team often tells me: "you need to test more with IE Frank!" My bad. Let's hope I got it right this time. I changed the code to shrink the particle a bit in each update:p.s -= 0.1This of course requires the particle to have a size when we create it:
p.s: 3.0With this setup the particle will slowly shrink from 3 pixels width and height to 0. Once the size of the particle reaches 0, we can kill it:
if (p.ttl-- > 0 && p.s > 0) { updateParticleElement(p);This leads to a working particle system, but somehow it's still a bit boring when all the particles start and shrink at the same speed. So let's make the size change random and instead of just shrinking, let's make the particles grow at first and then shrink until they die out. For the initialization the change is simple:
p.ds: Math.random()+0.1,This gives the particle a growth speed that is guaranteed to be positive. So instead of adding 0.1 each time we update the particle, we add the ds:
p.s += p.ds;If we leave this unchanged, the particle will grow forever - or at least until it's time to live runs out. To ensure the particle also starts shrinking at some point we give it a maximum size:
if (p.s > 5) p.ds *= -1;When the particle reaches its maximum size, we just flip the sign of the growth speed - so it becomes a shrink speed. After this the particle will keep shrinking until it reaches size zero at which point it dies out. By now I'm not really sure if the ttl property is still needed, as the particles already have an implicit time to live and get their randomness from the growth/shrink speed. But I left the ttl in as a safeguard against my own mistakes when manipulating the size. I wouldn't want to end up with an infinitely living and growing particle by mistake. If you would reconstruct the particle system at this point it is already quite lively and dynamic. Particles seem to be attracted by the bottom of your screen and particles seem to have a real life cycle, where they grow and shrink to die out. But this particle system is still a spectator sport. Let's see what happens when we give the user some control over what happens on screen.
Mouse control
As our last feature we want to give the user some control over the behavior of the particles. Specifically I want to accomplish two things:- particles originate close to the position of the mouse cursor
- particles move roughly in the direction that the mouse is moving
mx and my - the position of the mouse dmx and dmy - the direction and speed of the mouse's movementThese variables are global to the script, so they are not stored for each particle. These variables are updated whenever the user moves the mouse, by hooking the onmousemove event of the window. The mousemove handler is a bit tricky to read at first. But since the behavior of the particles only depends on the four variables mentioned here, I will not explain the mousemove handler of the code further. Now how do we use these variables when creating and updating a particle? The variables are actually only used when we create a new particle:
x: mx, y: my, dx: dmx + Math.random(), dy: dmy + Math.random() - 1,As you can see the code remain quite similar to how it was. We've just substituted our new "mouse dependent" variables for the previous hard coded and random values. We still make the dx and dy a bit random, since otherwise all particles would go in the same direction. The updating of the particles doesn't have to change for this behavior. The mouse just allows the user to control the position and the direction of the initial movement. After its creation each particle behaves as the rules of our system dictate, which ensures the particles still behave similar even with al their differing properties.
Summary
So in the first article we created a simple particle system in less then 50 lines of JavaScript code. The system was fully functional, but felt a bit static after you'd been looking at it for a while. In this second article we've added life to our particle system by adding by:- making the environment a bit more "physical" with our simple gravity emulation
- making the life cycle of the particles more complex, by making them grow and then shrink and die out
- allowing the user to control the particles, by hooking their position and movement up to the mouse movement
The code
// constants var NUM_PARTICLES = 50; var FPS = 20; var PARTICLE_LIFETIME = 4; // in seconds var ANIMATION_LIFETIME = 300; // in seconds // global variables var container; var particles = []; var framecount = 0; var mx = 20; // default origin, will be set to mouse position once the mouse moves var my = 20; var dmx = 1.0; // default movement, will be set to mouse movement once the mouse moves var dmy = -1.0 function onMouseMove(e) { var IE = document.all?true:false var nmx, nmy; if (IE) { // grab the x-y pos.s if browser is IE nmx = event.clientX + document.body.scrollLeft nmy = event.clientY + document.body.scrollTop } else { // grab the x-y pos.s if browser is NS nmx = e.pageX nmy = e.pageY } dmx = (nmx - mx) / 4; dmy = (nmy - my) / 4; mx = nmx; my = nmy; } function createParticle(container) { var p = { elm: createParticleElement(), x: mx, y: my, s: 0.0, // size dx: dmx + Math.random(), dy: dmy + Math.random() - 1, ds: Math.random()+0.1, ttl: PARTICLE_LIFETIME*FPS }; container.appendChild(p.elm); updateParticleElement(p); return p; } function createParticleElement() { var elm = document.createElement('span');; elm.style.border = '1px solid blue'; elm.style.position = 'absolute'; return elm; } function updateParticleElement(p) { p.elm.style.width = p.elm.style.height = p.elm.minWidth = p.elm.minHeight = Math.floor(p.s) + 'px'; // doesn't work on IE p.elm.style.left = Math.floor(p.x) + 'px'; p.elm.style.top = Math.floor(p.y) + 'px'; } function updateParticles() { for (var i=particles.length-1; i >= 0; i--) { var p = particles[i]; p.s += p.ds; if (p.s > 5) p.ds *= -1; p.x += p.dx; p.y += p.dy; p.dy += 0.1; // simulate gravity if (p.ttl-- > 0 && p.s > 0) { updateParticleElement(p); } else { container.removeChild(p.elm); particles[i] = createParticle(container); } } if (framecount++ < ANIMATION_LIFETIME*FPS) { setTimeout('updateParticles()', Math.floor(1000 / FPS)); } else { alert("All done. Reload to watch again."); } } window.onload = function() { container = document.getElementById('particles'); for (var i=0; i < NUM_PARTICLES; i++) { particles[i] = createParticle(container); } setTimeout('updateParticles()', Math.floor(1000 / FPS)); document.onmousemove = onMouseMove; }Download it here. And the corresponding HTML:
<html> <head> <title>JavaScript Particle System</title> <script type="text/javascript" src="particles2.js"> </script> </head> <body> <div id="particles" style="width:640px; height: 480px; overflow: hidden"> </div> </body> </html>Download it here.
15 comments:
Excellent. Sure is fun to play with! Keep up the great work!!!
not sure how to modify this script to make it stationary instead of follow the mouse
Very cool experiment with particles and html5 canvas. Thought you might appreciate this.
http://9elements.com/io/?p=153
The easiest way to stop this script from following the mouse is to remove this line:
document.onmousemove = onMouseMove;
After that the particles will originate near the top left and move to the bottom right.
Thanks for the link. They are sure making good use of the new canvas tag in HTML5 there.
I am designing my portfolio site and I had a great idea. What if the particles had gravity and bounced on the surface seen here...
http://emacmedia.com/
How hard would that be? Am I crazy? Ok I can answer that one. But that would be one "arkin' and sparkin'" website design!
-Eric
Should not be too hard to do. If you know the X (for horizontal) or Y (for vertical) coordinate that you want them to bounce of.
When the particle passes the boundary, you can just invert the X (or Y) - maybe reducing it a bit to show a dampening.
if (particle.y > SURFACE_Y) {
particle.y = -0.75 * particle.y;
}
Could be cool actually. Let me know if you need a hand with it.
You are in another league, just so you know. I'm looking at my site and it will require a little work with layers in the css because the logo is on the same layer as the surface. I need to separate them out so I can put the particles in between. I dont think IE6 will like me very much ;). The real issue is the surface has a perspective to it and if I set the 'surface' at a certain value, the particles will be bouncing at the same pixel level every time. At that point I guess I am talking about 3d particles if I want the particles to go bouncing across the perspective surface.
As long as the particles are just moving left-to-right you might be able to stay away from the 3d stuff.
You could for example create two layers of particles: one in front of the text and one behind it. Both could be simple 2d particle systems, much like you have now - but then bouncing of course. :-)
Great news, I got it to work.
A little bit different syntax to match how you wrote the second script.
Only problem with the growing and shrinking particles is that the particles can only be so small before they disappear at which point the particle life doesn't really matter anymore.
I managed to figure this out just fine. Boy is it fun to play with the bounce. For a realistic bounce set the coefficient to .9.
I had a great idea for maybe reflections, or even particle generation on bounce!
Hope you enjoy this as much as I do! Thanks for the help!
See my work in progress...
-Eric
That looks awesome Eric. Great job and glad that I could help a bit.
Keep me posted on progress you make and let me know if you get stuck. Getting more people interested in particle systems is why I wrote these posts in the first place. :-)
Looks like someone is welding back there lol.
Over a year later, but look how html5 is going to take the web to the next level. I knew Flash was going to have to go the way of the dodo some time.
HTML5 particles »
I'll let you know how my version turns out!
It indeed seems like having a Canvas available in the browser is getting more people interested in particle systems. Which is great as far as I'm concerned.
I played with Canvas particles a while back. I was trying to get an effect of bubbles floating upwards through water. But I wasn't too happy with the performance I got using a canvas, so in the end settled on using images with very low alpha values. Fun!
I should post that particle system too, but life keeps getting in the way. :-/
Post a Comment