<!-- begin snippet: js hide: true --> <!-- language: lang-js --> var envelopeHeight = 100; var fallRange = 8.5; var fallMidpoint = 75; var maxOnScreen = 6; var frequency = 4500; //time between falls var initialVelocity = 0.2; var gravity = 0.6; var friction = 0.97; var dampening = 2; var lastEnvelope = 0; var envelopes = []; function onFrame() { var onScreen = 0; for (var i = 0; i < envelopes.length; i++) { if (!envelopes[i].dead) { onScreen++; } envelopes[i].updateSelf(); } if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) { var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100); var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight); envelope.position = new Point(dropX, -envelopeHeight); envelope.fillColor = { hue: Math.random() * 360, saturation: 1, brightness: 1 } envelope.velocity = initialVelocity; envelope.removed = false; envelope.updateSelf = function() { if (this.bounds.bottom > view.bounds.height + 1000) { this.remove(); envelopes.splice(i,1); return; }; function bounce() { if (this.velocity > dampening) { this.velocity -= dampening; } else { this.velocity = 0; } this.velocity = -this.velocity; }; if (this.bounds.bottom >= view.bounds.height && !this.dead) { bounce.apply(this); } for (var i = 0;i<envelopes.length;i++) { var bottomLeft = new Point(this.bounds.left, this.bounds.bottom); var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom); if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) { bounce.apply(this); }; if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) { bounce.apply(this); }; } this.position.y += this.velocity; this.velocity += gravity; this.velocity *= friction; } envelopes.push(envelope); lastEnvelope = Date.now(); }; }; function onMouseDown() { for (var i = 0;i<envelopes.length;i++) { envelopes[i].dead = true; } } <!-- language: lang-css --> #envelopes { height: 100%; width: 100% } <!-- language: lang-html --> <script src="http://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.18/paper-full.min.js"></script> <script type="text/paperscript" canvas="envelopes" resize> var envelopeHeight = 100; var fallRange = 8.5; var fallMidpoint = 75; var maxOnScreen = 6; var frequency = 4500; //time between falls var initialVelocity = 0.2; var gravity = 0.6; var friction = 0.97; var dampening = 2; var lastEnvelope = 0; var envelopes = []; function onFrame() { var onScreen = 0; for (var i = 0; i < envelopes.length; i++) { if (!envelopes[i].dead) { onScreen++; } envelopes[i].updateSelf(); } if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) { var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100); var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight); envelope.position = new Point(dropX, -envelopeHeight); envelope.fillColor = { hue: Math.random() * 360, saturation: 1, brightness: 1 } envelope.velocity = initialVelocity; envelope.removed = false; envelope.updateSelf = function() { if (this.bounds.bottom > view.bounds.height + 1000) { this.remove(); envelopes.splice(i,1); return; }; function bounce() { if (this.velocity > dampening) { this.velocity -= dampening; } else { this.velocity = 0; } this.velocity = -this.velocity; }; if (this.bounds.bottom >= view.bounds.height && !this.dead) { bounce.apply(this); } for (var i = 0;i<envelopes.length;i++) { var bottomLeft = new Point(this.bounds.left, this.bounds.bottom); var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom); if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) { bounce.apply(this); }; if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) { bounce.apply(this); }; } this.position.y += this.velocity; this.velocity += gravity; this.velocity *= friction; } envelopes.push(envelope); lastEnvelope = Date.now(); }; }; function onMouseDown() { for (var i = 0;i<envelopes.length;i++) { envelopes[i].dead = true; } } </script> <canvas id="envelopes"></canvas> <!-- end snippet --> 1. I'm not sure if it would be better to make the envelope into an extension of the Rectangle constructor. 2. The physics seem a bit dodgy, it works well most of the time, but sometimes the boxes overlap and sometimes the bouncing doesn't work properly. <!--- ---> var envelopeHeight = 100; var fallRange = 8.5; var fallMidpoint = 75; var maxOnScreen = 6; var frequency = 4500; //time between falls var initialVelocity = 0.2; var gravity = 0.6; var friction = 0.97; var dampening = 2; var lastEnvelope = 0; var envelopes = []; function onFrame() { var onScreen = 0; for (var i = 0; i < envelopes.length; i++) { if (!envelopes[i].dead) { onScreen++; } envelopes[i].updateSelf(); } if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) { var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100); var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight); envelope.position = new Point(dropX, -envelopeHeight); envelope.fillColor = { hue: Math.random() * 360, saturation: 1, brightness: 1 } envelope.velocity = initialVelocity; envelope.removed = false; envelope.updateSelf = function() { if (this.bounds.bottom > view.bounds.height + 1000) { this.remove(); envelopes.splice(i,1); return; }; function bounce() { if (this.velocity > dampening) { this.velocity -= dampening; } else { this.velocity = 0; } this.velocity = -this.velocity; }; if (this.bounds.bottom >= view.bounds.height && !this.dead) { bounce.apply(this); } for (var i = 0;i<envelopes.length;i++) { var bottomLeft = new Point(this.bounds.left, this.bounds.bottom); var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom); if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) { bounce.apply(this); }; if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) { bounce.apply(this); }; } this.position.y += this.velocity; this.velocity += gravity; this.velocity *= friction; } envelopes.push(envelope); lastEnvelope = Date.now(); }; }; function onMouseDown() { for (var i = 0;i<envelopes.length;i++) { envelopes[i].dead = true; } } <!-- --> [1]:http://sketch.paperjs.org/#S/tVZdT9swFP0rpg8oJSWUbcBWCC9j0x6GNsE0TaI8uMltY5HaWey0Q6j/fddOHDuhUO1hL205Pvf72JenAadLGEwGtw+gkmwwGiQi1X+vaEmAryAXBXwBtsgUicnxeHw+5fpoTvP8hvIFIPo+OvHQa5YWgnFNP7P4kv75xm+TEoAjfGrZJfyugCePiL07Qdfk6EixJZAZqLWmaney5jLOFKP5T8wnYUpbjKM36Kc+XZR0ZVHnnSWKCW7AD2cNmtJlAZzxBcLOPqdSfWqK1fyGbOuXiN3dG/a84rVXwT+X2LlgSJ4QV5ouXInaw1TNRUkCk72B8OvC+Yxy4AuVIRqGjZOpYnMS7LWUO3YfpUDT9niqbIwwPK+hTf3VsamKlCq4hXweDA3NkLTvK8QjLtaY9mW36tAbx/6+q+Ui9qfXZmJ6WYriF1a2YrCOZqLiqYzWLFUZOSBBEFxTlUUl5alYYrgDJ5mhDuYpZUiOtLKG5841uGFwWJPv2tUNJArNcwgMpC0Dk8KIHHaVOhxpf6Oefq1/i0aFkKxRyE6Pfds5y/OPIscBx244WQUT0i/77el4ZAmSqqqkOuaEHLforNQhOEiJ6NapRiun+95N6CdWwlKsIEUitlhC/9hJQzMaMQeewLRKVMakHehMKCWWqBZ/yln9IIS6zWPPeKqMaZ1DYJvW0Wcki5wlELDRsX9eAnaGt8Cm/dVeOB06gaATrU227c+lu+EdZpNZyzuMHdFLY0MAm/aaob3bDb9XuUc77AB+ZbUB39XxeFvL8WruGXLvXUAVmf5EtCjyR+PPtXfjgvWeJHbx7D3ynyN7HeuUvsJcde6Kn3WOhyPyvA5/yM7VTbNQXvaF4vIh867s8q872XkIM6Z+gFSBy3+oO9jhsHQvNm5Z2lPMKx31Fbozrin2PwT2xmr82PcseiRhTLbLr6tSpDWr8wXCQdzuUafbLVsnKiqZtT2w6fbWqls+9VLCz01/qV6LSsKVWLdv0r8J9tnqRCNVVmC34Ab/wZmVQB/M4pGDyd395i8=