snow.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. // Make it snow in your site
  2. // See https://github.com/Canop/snow
  3. window.snow = (function(){
  4. var ctx,
  5. canvas,
  6. W,
  7. H,
  8. heights = [],
  9. n = 0,
  10. timer,
  11. fall;
  12. function resetScreen(){
  13. if (!canvas) {
  14. window.addEventListener("resize", resetScreen);
  15. canvas = document.createElement("canvas");
  16. document.body.appendChild(canvas);
  17. }
  18. W = window.innerWidth;
  19. H = window.innerHeight;
  20. canvas.id = "snow-canvas";
  21. canvas.style.pointerEvents = "none";
  22. canvas.style.position = "fixed";
  23. canvas.style.zIndex = 5000; // todo make it modifiable
  24. canvas.style.left = 0;
  25. canvas.style.right = 0;
  26. canvas.style.top = 0;
  27. canvas.style.bottom = 0;
  28. canvas.width = W;
  29. canvas.height = H;
  30. ctx = canvas.getContext("2d");
  31. if (heights.length<W) {
  32. var i = 0,
  33. nh = new Array(W);
  34. for (;i<heights.length; i++) nh[i] = heights[i];
  35. for (;i<W; i++) nh[i] = 0;
  36. heights = nh;
  37. }
  38. }
  39. function rnd(min, max){
  40. if (max === undefined) {
  41. max = min;
  42. min = 0;
  43. }
  44. return min + Math.random()*(max-min);
  45. }
  46. function Fall(options){
  47. this.flakes = new Array(Math.ceil(options.flakeCount) || 400);
  48. this.maxRadius = options.maxRadius || 1.7;
  49. this.wind = options.wind || 0;
  50. this.color = options.color || "#fff";
  51. this.minSpeed = options.minSpeed || 1;
  52. this.maxSpeed = options.maxSpeed || 4.2;
  53. this.stickingRatio = options.stickingRatio || .4;
  54. this.maxHeightRatio = options.maxHeightRatio || .25;
  55. this.dying = false;
  56. for (var i=0; i<this.flakes.length; i++) this.flakes[i] = new Flake(this);
  57. }
  58. Fall.prototype.draw = function(){
  59. ctx.fillStyle = this.color;
  60. ctx.strokeStyle = this.color;
  61. for (var i=0; i<this.flakes.length; i++) {
  62. this.flakes[i].draw();
  63. }
  64. }
  65. Fall.prototype.update = function(){
  66. for (var i=0; i<this.flakes.length; i++) {
  67. if (this.flakes[i].update(this)) {
  68. this.flakes[i] = null;
  69. }
  70. }
  71. if (this.dying) {
  72. this.flakes = this.flakes.filter(function(v){
  73. return v;
  74. });
  75. if (!this.flakes.length) fall = null;
  76. }
  77. if (n%2) {
  78. for (var i=1; i<W; i++) {
  79. var d = heights[i]-heights[i-1];
  80. if (d>1) {
  81. heights[i] -=.7*d;
  82. heights[i-1] +=.3*d;
  83. if (i>1) heights[i-2] +=.2*d;
  84. if (i>2) heights[i-3] +=.1*d;
  85. }
  86. }
  87. } else {
  88. for (var i=0; i<W-1; i++) {
  89. var d = heights[i]-heights[i+1];
  90. if (d>1) {
  91. heights[i] -=.7*d;
  92. heights[i+1] +=.3*d;
  93. if (i<W-2) heights[i+2] +=.2*d;
  94. if (i<W-3) heights[i+3] +=.1*d;
  95. }
  96. }
  97. }
  98. n++;
  99. }
  100. Fall.prototype.rndX = function(){
  101. var windRange = this.wind*H/this.minSpeed;
  102. return rnd(Math.min(0, -windRange)-10, Math.max(W, W-windRange)+10);
  103. }
  104. function Flake(fall){
  105. this.y = rnd(-H, 0);
  106. this.x = fall.rndX();
  107. this.radius = rnd(1, fall.maxRadius);
  108. if (fall.maxRadius>1) {
  109. this.speed = fall.minSpeed + (fall.maxSpeed-fall.minSpeed)*(this.radius-1)/(fall.maxRadius-1);
  110. } else {
  111. this.speed = rnd(fall.minSpeed, fall.maxSpeed);
  112. }
  113. this.omega = rnd(.02, .13);
  114. }
  115. Flake.prototype.draw = function(){
  116. ctx.beginPath();
  117. ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
  118. ctx.fill();
  119. }
  120. Flake.prototype.update = function(fall){
  121. this.y += this.speed;
  122. var i = Math.round(this.x),
  123. h;
  124. if (i<0) h = heights[0];
  125. else if (i>=W) h = heights[W-1];
  126. else h = heights[i];
  127. if (this.y >= H-h) {
  128. if (i>=0 && i<W) {
  129. if (h<H*fall.maxHeightRatio) {
  130. heights[i] += this.radius * fall.stickingRatio;
  131. }
  132. }
  133. if (fall.dying && Math.random()<.7) {
  134. return true; // flake disapears
  135. }
  136. this.y = rnd(-30, 1);
  137. this.x = fall.rndX();
  138. return;
  139. }
  140. this.x += fall.wind;
  141. this.x += Math.cos(this.y*this.omega/this.speed);
  142. }
  143. function drawGround(){
  144. ctx.beginPath();
  145. ctx.moveTo(0, H);
  146. for (var i=0; i<heights.length; i++) {
  147. ctx.lineTo(i, H-heights[i]);
  148. }
  149. ctx.lineTo(W+1, H-heights[heights.length-1]);
  150. ctx.lineTo(W+1, H);
  151. ctx.lineTo(0, H);
  152. ctx.fill();
  153. }
  154. function draw(){
  155. ctx.clearRect(0, 0, W, H);
  156. drawGround();
  157. if (fall) {
  158. fall.draw();
  159. fall.update();
  160. clearTimeout(timer);
  161. timer = setTimeout(draw, 25);
  162. }
  163. }
  164. return {
  165. start: function(options){
  166. if (!ctx) resetScreen();
  167. fall = new Fall(options||{});
  168. running = true;
  169. draw();
  170. },
  171. stop: function(){
  172. if (fall) fall.dying = true;
  173. },
  174. ground: function(){
  175. return heights;
  176. }
  177. }
  178. })();