|
@@ -0,0 +1,187 @@
|
|
|
+// Make it snow in your site
|
|
|
+// See https://github.com/Canop/snow
|
|
|
+window.snow = (function(){
|
|
|
+
|
|
|
+ var ctx,
|
|
|
+ canvas,
|
|
|
+ W,
|
|
|
+ H,
|
|
|
+ heights = [],
|
|
|
+ n = 0,
|
|
|
+ timer,
|
|
|
+ fall;
|
|
|
+
|
|
|
+ function resetScreen(){
|
|
|
+ if (!canvas) {
|
|
|
+ window.addEventListener("resize", resetScreen);
|
|
|
+ canvas = document.createElement("canvas");
|
|
|
+ document.body.appendChild(canvas);
|
|
|
+ }
|
|
|
+ W = window.innerWidth;
|
|
|
+ H = window.innerHeight;
|
|
|
+ canvas.id = "snow-canvas";
|
|
|
+ canvas.style.pointerEvents = "none";
|
|
|
+ canvas.style.position = "fixed";
|
|
|
+ canvas.style.zIndex = 5000; // todo make it modifiable
|
|
|
+ canvas.style.left = 0;
|
|
|
+ canvas.style.right = 0;
|
|
|
+ canvas.style.top = 0;
|
|
|
+ canvas.style.bottom = 0;
|
|
|
+ canvas.width = W;
|
|
|
+ canvas.height = H;
|
|
|
+ ctx = canvas.getContext("2d");
|
|
|
+ if (heights.length<W) {
|
|
|
+ var i = 0,
|
|
|
+ nh = new Array(W);
|
|
|
+ for (;i<heights.length; i++) nh[i] = heights[i];
|
|
|
+ for (;i<W; i++) nh[i] = 0;
|
|
|
+ heights = nh;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function rnd(min, max){
|
|
|
+ if (max === undefined) {
|
|
|
+ max = min;
|
|
|
+ min = 0;
|
|
|
+ }
|
|
|
+ return min + Math.random()*(max-min);
|
|
|
+ }
|
|
|
+
|
|
|
+ function Fall(options){
|
|
|
+ this.flakes = new Array(Math.ceil(options.flakeCount) || 400);
|
|
|
+ this.maxRadius = options.maxRadius || 1.7;
|
|
|
+ this.wind = options.wind || 0;
|
|
|
+ this.color = options.color || "#fff";
|
|
|
+ this.minSpeed = options.minSpeed || 1;
|
|
|
+ this.maxSpeed = options.maxSpeed || 4.2;
|
|
|
+ this.stickingRatio = options.stickingRatio || .4;
|
|
|
+ this.maxHeightRatio = options.maxHeightRatio || .25;
|
|
|
+ this.dying = false;
|
|
|
+ for (var i=0; i<this.flakes.length; i++) this.flakes[i] = new Flake(this);
|
|
|
+ }
|
|
|
+ Fall.prototype.draw = function(){
|
|
|
+ ctx.fillStyle = this.color;
|
|
|
+ ctx.strokeStyle = this.color;
|
|
|
+ for (var i=0; i<this.flakes.length; i++) {
|
|
|
+ this.flakes[i].draw();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Fall.prototype.update = function(){
|
|
|
+ for (var i=0; i<this.flakes.length; i++) {
|
|
|
+ if (this.flakes[i].update(this)) {
|
|
|
+ this.flakes[i] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (this.dying) {
|
|
|
+ this.flakes = this.flakes.filter(function(v){
|
|
|
+ return v;
|
|
|
+ });
|
|
|
+ if (!this.flakes.length) fall = null;
|
|
|
+ }
|
|
|
+ if (n%2) {
|
|
|
+ for (var i=1; i<W; i++) {
|
|
|
+ var d = heights[i]-heights[i-1];
|
|
|
+ if (d>1) {
|
|
|
+ heights[i] -=.7*d;
|
|
|
+ heights[i-1] +=.3*d;
|
|
|
+ if (i>1) heights[i-2] +=.2*d;
|
|
|
+ if (i>2) heights[i-3] +=.1*d;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for (var i=0; i<W-1; i++) {
|
|
|
+ var d = heights[i]-heights[i+1];
|
|
|
+ if (d>1) {
|
|
|
+ heights[i] -=.7*d;
|
|
|
+ heights[i+1] +=.3*d;
|
|
|
+ if (i<W-2) heights[i+2] +=.2*d;
|
|
|
+ if (i<W-3) heights[i+3] +=.1*d;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+ Fall.prototype.rndX = function(){
|
|
|
+ var windRange = this.wind*H/this.minSpeed;
|
|
|
+ return rnd(Math.min(0, -windRange)-10, Math.max(W, W-windRange)+10);
|
|
|
+ }
|
|
|
+
|
|
|
+ function Flake(fall){
|
|
|
+ this.y = rnd(-H, 0);
|
|
|
+ this.x = fall.rndX();
|
|
|
+ this.radius = rnd(1, fall.maxRadius);
|
|
|
+ if (fall.maxRadius>1) {
|
|
|
+ this.speed = fall.minSpeed + (fall.maxSpeed-fall.minSpeed)*(this.radius-1)/(fall.maxRadius-1);
|
|
|
+ } else {
|
|
|
+ this.speed = rnd(fall.minSpeed, fall.maxSpeed);
|
|
|
+ }
|
|
|
+ this.omega = rnd(.02, .13);
|
|
|
+ }
|
|
|
+ Flake.prototype.draw = function(){
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
|
|
|
+ ctx.fill();
|
|
|
+ }
|
|
|
+ Flake.prototype.update = function(fall){
|
|
|
+ this.y += this.speed;
|
|
|
+ var i = Math.round(this.x),
|
|
|
+ h;
|
|
|
+ if (i<0) h = heights[0];
|
|
|
+ else if (i>=W) h = heights[W-1];
|
|
|
+ else h = heights[i];
|
|
|
+ if (this.y >= H-h) {
|
|
|
+ if (i>=0 && i<W) {
|
|
|
+ if (h<H*fall.maxHeightRatio) {
|
|
|
+ heights[i] += this.radius * fall.stickingRatio;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (fall.dying && Math.random()<.7) {
|
|
|
+ return true; // flake disapears
|
|
|
+ }
|
|
|
+ this.y = rnd(-30, 1);
|
|
|
+ this.x = fall.rndX();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.x += fall.wind;
|
|
|
+ this.x += Math.cos(this.y*this.omega/this.speed);
|
|
|
+ }
|
|
|
+
|
|
|
+ function drawGround(){
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(0, H);
|
|
|
+ for (var i=0; i<heights.length; i++) {
|
|
|
+ ctx.lineTo(i, H-heights[i]);
|
|
|
+ }
|
|
|
+ ctx.lineTo(W+1, H-heights[heights.length-1]);
|
|
|
+ ctx.lineTo(W+1, H);
|
|
|
+ ctx.lineTo(0, H);
|
|
|
+ ctx.fill();
|
|
|
+ }
|
|
|
+
|
|
|
+ function draw(){
|
|
|
+ ctx.clearRect(0, 0, W, H);
|
|
|
+ drawGround();
|
|
|
+ if (fall) {
|
|
|
+ fall.draw();
|
|
|
+ fall.update();
|
|
|
+ clearTimeout(timer);
|
|
|
+ timer = setTimeout(draw, 25);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ start: function(options){
|
|
|
+ if (!ctx) resetScreen();
|
|
|
+ fall = new Fall(options||{});
|
|
|
+ running = true;
|
|
|
+ draw();
|
|
|
+ },
|
|
|
+ stop: function(){
|
|
|
+ if (fall) fall.dying = true;
|
|
|
+ },
|
|
|
+ ground: function(){
|
|
|
+ return heights;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+})();
|