Edit: Raphael is definitely better suited for this, since it supports IE. The problem with jQuery is that rounded corners are a pain that needs to be done in IE due to CSS limitations ... there is no sweat in the circles of raster browsers Raphael.
(although it may look better accelerated in IE )
(function() { var paper, circs, i, nowX, nowY, timer, props = {}, toggler = 0, elie, dx, dy, rad, cur, opa; // Returns a random integer between min and max // Using Math.round() will give you a non-uniform distribution! function ran(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function moveIt() { for(i = 0; i < circs.length; ++i) { // Reset when time is at zero if (! circs[i].time) { circs[i].time = ran(30, 100); circs[i].deg = ran(-179, 180); circs[i].vel = ran(1, 5); circs[i].curve = ran(0, 1); circs[i].fade = ran(0, 1); circs[i].grow = ran(-2, 2); } // Get position nowX = circs[i].attr("cx"); nowY = circs[i].attr("cy"); // Calc movement dx = circs[i].vel * Math.cos(circs[i].deg * Math.PI/180); dy = circs[i].vel * Math.sin(circs[i].deg * Math.PI/180); // Calc new position nowX += dx; nowY += dy; // Calc wrap around if (nowX < 0) nowX = 490 + nowX; else nowX = nowX % 490; if (nowY < 0) nowY = 490 + nowY; else nowY = nowY % 490; // Render moved particle circs[i].attr({cx: nowX, cy: nowY}); // Calc growth rad = circs[i].attr("r"); if (circs[i].grow > 0) circs[i].attr("r", Math.min(30, rad + .1)); else circs[i].attr("r", Math.max(10, rad - .1)); // Calc curve if (circs[i].curve > 0) circs[i].deg = circs[i].deg + 2; else circs[i].deg = circs[i].deg - 2; // Calc opacity opa = circs[i].attr("fill-opacity"); if (circs[i].fade > 0) { circs[i].attr("fill-opacity", Math.max(.3, opa - .01)); circs[i].attr("stroke-opacity", Math.max(.3, opa - .01)); } else { circs[i].attr("fill-opacity", Math.min(1, opa + .01)); circs[i].attr("stroke-opacity", Math.min(1, opa + .01)); } // Progress timer for particle circs[i].time = circs[i].time - 1; // Calc damping if (circs[i].vel < 1) circs[i].time = 0; else circs[i].vel = circs[i].vel - .05; } timer = setTimeout(moveIt, 60); } window.onload = function () { paper = Raphael("canvas", 500, 500); circs = paper.set(); for (i = 0; i < 30; ++i) { opa = ran(3,10)/10; circs.push(paper.circle(ran(0,500), ran(0,500), ran(10,30)).attr({"fill-opacity": opa, "stroke-opacity": opa})); } circs.attr({fill: "#00DDAA", stroke: "#00DDAA"}); moveIt(); elie = document.getElementById("toggle"); elie.onclick = function() { (toggler++ % 2) ? (function(){ moveIt(); elie.value = " Stop "; }()) : (function(){ clearTimeout(timer); elie.value = " Start "; }()); } }; }());
The first attempt to solve jQuery is below:
This jQuery attempt almost doesn't work in IE and is slow in FF. Chrome and Safari succeed:
(I did not implement fade in IE, and IE does not have rounded corners ... also JS is slower, so it looks pretty bad)
JsFiddle example for Chrome and Safari (only 4 times as many particles)
(function() { var x, y, $elie, pos, nowX, nowY, i, $that, vel, deg, fade, curve, ko, mo, oo, grow, len; // Returns a random integer between min and max // Using Math.round() will give you a non-uniform distribution! function ran(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function moveIt() { $("div.spec").each(function(i, v) { $elie = $(v); if (! $elie.data("time")) { $elie.data("time", ran(30, 100)); $elie.data("deg", ran(-179, 180)); $elie.data("vel", ran(3, 10)); $elie.data("curve", ran(0, 1)); $elie.data("fade", ran(0, 1)); $elie.data("grow", ran(-2, 2)); } vel = $elie.data("vel"); deg = $elie.data("deg"); fade = $elie.data("fade"); curve = $elie.data("curve"); grow = $elie.data("grow"); len = $elie.width(); if (grow > 0) len = Math.min(len + grow, 50); else len = Math.max(len + grow, 20); $elie.css("-moz-border-radius", len/2); $elie.css("border-radius", len/2); $elie.css("width", len); $elie.css("height", len); pos = $elie.position(); $elie.data("time", $elie.data("time") - 1); if (curve) $elie.data("deg", (deg + 5) % 180); else $elie.data("deg", (deg - 5) % 180); ko = $elie.css("-khtml-opacity"); mo = $elie.css("-moz-opacity"); oo = $elie.css("opacity"); if (fade) { $elie.css("-khtml-opacity", Math.max(ko - .1, .5)); $elie.css("-moz-opacity", Math.max(mo - .1, .5)); $elie.css("opacity", Math.max(oo - .1, .5)); } else { $elie.css("-khtml-opacity", Math.min(ko - -.1, 1)); $elie.css("-moz-opacity", Math.min(mo - -.1, 1)); $elie.css("opacity", Math.min(oo - -.1, 1)); } if (vel < 3) $elie.data("time", 0); else $elie.data("vel", vel - .2); nowX = pos.left; nowY = pos.top; x = vel * Math.cos(deg * Math.PI/180); y = vel * Math.sin(deg * Math.PI/180); nowX = nowX + x; nowY = nowY + y; if (nowX < 0) nowX = 490 + nowX; else nowX = nowX % 490; if (nowY < 0) nowY = 490 + nowY; else nowY = nowY % 490; $elie.css("left", nowX); $elie.css("top", nowY); }); } $(function() { $(document.createElement('div')).appendTo('body').attr('id', 'box'); $elie = $("<div/>").attr("class","spec"); // Note that math random is inclussive for 0 and exclussive for Max for (i = 0; i < 100; ++i) { $that = $elie.clone(); $that.css("top", ran(0, 495)); $that.css("left", ran(0, 495)); $("#box").append($that); } timer = setInterval(moveIt, 60); $("input").toggle(function() { clearInterval(timer); this.value = " Start "; }, function() { timer = setInterval(moveIt, 60); this.value = " Stop "; }); }); }());