├── Gruntfile.js ├── LICENSE ├── README.md ├── demo ├── bg.jpg ├── index.html └── index2.html ├── magic-canvas.js ├── magic-canvas.min.js └── package.json /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | concat: { 7 | options: { 8 | }, 9 | dist: { 10 | src: ['magic-canvas.js'], 11 | dest: '<%= pkg.name %>.min.js' 12 | } 13 | }, 14 | uglify: { 15 | main: { 16 | src: '<%= pkg.name %>.min.js', 17 | dest: '<%= pkg.name %>.min.js' 18 | } 19 | }, 20 | banner: '/*!\n' + 21 | ' * <%= pkg.title %> v<%= pkg.version %> (<%= pkg.homepage %>)\n' + 22 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + 23 | ' */\n', 24 | usebanner: { 25 | dist: { 26 | options: { 27 | position: 'top', 28 | banner: '<%= banner %>' 29 | }, 30 | files: { 31 | src: ['js/<%= pkg.name %>.min.js'] 32 | } 33 | } 34 | }, 35 | watch: { 36 | scripts: { 37 | files: ['*.js'], 38 | tasks: ['concat', 'uglify', 'usebanner'], 39 | options: { 40 | spawn: false 41 | } 42 | } 43 | } 44 | }); 45 | 46 | // Load the plugins. 47 | grunt.loadNpmTasks('grunt-contrib-uglify'); 48 | grunt.loadNpmTasks('grunt-contrib-concat'); 49 | grunt.loadNpmTasks('grunt-contrib-less'); 50 | grunt.loadNpmTasks('grunt-banner'); 51 | grunt.loadNpmTasks('grunt-contrib-watch'); 52 | 53 | // Default task(s). 54 | grunt.registerTask('default', ['concat', 'uglify', 'usebanner']); 55 | 56 | }; 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2016] [decaywood] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [demo1 -- heartbeat](http://7xqo9j.com1.z0.glb.clouddn.com/MagicCanvas%2Findex.html?v=0.5-beta) 2 | 3 | [demo2 -- random move](http://7xqo9j.com1.z0.glb.clouddn.com/MagicCanvas%2Findex2.html?v=0.5-beta) 4 | 5 | # Dependencies 6 | - 7 | magic canvas depends on jQuery. Include them both in end of your HTML code (TweenLite.min.js is optional ,if you use random-move effect, it should be added): 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | ``` 15 | 16 | # Something you should know 17 | 18 | the effect needs considerable performance. originally, it's running only on the browsers that requestAnimationFrame is built in. with [polyfill](https://remysharp.com/2010/10/08/what-is-a-polyfill), the early browser could work as well, but the best choice is to update the browser to the latest version rather than use fall-backed requestAnimationFrame. 19 | 20 | **browsers supporting for requestAnimationFrame**: 21 | 22 | * chrome 31+ 23 | * firefox 26+ 24 | * IE 10+ 25 | * opera 19+ 26 | * safari 6+ 27 | 28 | 29 | # Use like so: 30 | 31 | demo1: heart-beat effect 32 | 33 | ```javascript 34 | $.magicCanvas.draw({ 35 | lineLen : 30, // length of hive's side 36 | heartBeatCD : 3000, // boom period 37 | heartBeatRange : 300, // boom range 38 | rgb : function (circlePos, heartBeatCenter) { 39 | var px = circlePos.x; // a point on boom circle 40 | var py = circlePos.y; 41 | var hbcx = heartBeatCenter.x; 42 | var hbcy = heartBeatCenter.y; 43 | 44 | var dis = Math.pow((px - hbcx), 2) + Math.pow((py - hbcy), 2); 45 | var maxDis = 300 * 300; 46 | 47 | var r = parseInt(255 * dis / maxDis); 48 | // do some computation.... 49 | return {r:r,g:217,b:203}; 50 | }, 51 | // rgb: {r:156,g:217,b:249}; // parameter rgb can be a object as well 52 | zIndex:-9999 // stack order 53 | }) 54 | ``` 55 | 56 | demo2: random-move 57 | 58 | ```javascript 59 | $.magicCanvas.draw({ 60 | type:"random-move", 61 | rgb : function (circlePos) { 62 | var px = circlePos.x; // point position 63 | var py = circlePos.y; 64 | // do some computation.... 65 | return {r:parseInt(px % 255),g:parseInt(py % 255),b:203}; 66 | }, 67 | zIndex = -9999; // stack order 68 | }) 69 | ``` 70 | 71 | you can click the link on the top of the page to see the demo. 72 | 73 | # LICENSE 74 | -------------------------------------------------------------------------------- /demo/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decaywood/MagicCanvas/133db5884c02fd7bc2cda33b92bced8c2b699dff/demo/bg.jpg -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Magic Canvas 6 | 23 | 24 | 25 |

Magic Canvas

26 |



by decaywood

27 | 28 | 29 | 30 | 31 | 32 | 33 | 58 | -------------------------------------------------------------------------------- /demo/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Magic Canvas 6 | 23 | 24 | 25 |

Magic Canvas

26 |



by decaywood

27 | 28 | 29 | 30 | 31 | 32 | 33 | 47 | -------------------------------------------------------------------------------- /magic-canvas.js: -------------------------------------------------------------------------------- 1 | jQuery.magicCanvas = { 2 | reqId: 0, 3 | draw: function (opt) { 4 | 5 | polyFill(); 6 | 7 | var width, 8 | height, 9 | canvas, 10 | ctx, 11 | points, 12 | target, 13 | draw = true, 14 | intersections = [], 15 | $canvas = $("#reactive-bg-canvas"); 16 | 17 | var defaults = { 18 | lineLen: 30, 19 | heartBeatCD: 3000, 20 | heartBeatRange: 300, 21 | rgb: {r: 156, g: 217, b: 249}, 22 | type: "heart-beat", 23 | zIndex: -99999 24 | }; 25 | 26 | var options = $.extend(defaults, opt); 27 | 28 | $(document).mouseenter(function () { 29 | draw = true; 30 | }); 31 | 32 | $(document).mouseleave(function () { 33 | draw = false; 34 | }); 35 | 36 | // Main 37 | initMap(); 38 | initAnimation(); 39 | addListeners(); 40 | 41 | function initMap() { 42 | 43 | canvas = document.getElementById("reactive-bg-canvas"); 44 | 45 | width = $(window).width(); 46 | height = $(window).height(); 47 | canvas.style.position = "fixed"; 48 | canvas.style.zIndex = options.zIndex; 49 | canvas.style.top = '0px'; 50 | canvas.style.left = '0px'; 51 | 52 | canvas.width = width; 53 | canvas.height = height; 54 | 55 | target = {x: width / 2, y: height / 2, rx: width / 2, ry: height / 2}; 56 | 57 | ctx = canvas.getContext("2d"); 58 | 59 | createMap(); 60 | 61 | } 62 | 63 | // Event handling 64 | function addListeners() { 65 | if (!("ontouchstart" in window)) { 66 | window.addEventListener("mousemove", mouseMove); 67 | } 68 | window.addEventListener("scroll", scroll); 69 | window.addEventListener("resize", resize); 70 | } 71 | 72 | function scroll() { 73 | target.x = target.rx + document.body.scrollLeft + document.documentElement.scrollLeft; 74 | target.y = target.ry + document.body.scrollTop + document.documentElement.scrollTop; 75 | } 76 | 77 | function mouseMove(e) { 78 | 79 | if (e.pageX || e.pageY) { 80 | target.x = e.pageX; 81 | target.y = e.pageY; 82 | } 83 | else if (e.clientX || e.clientY) { 84 | target.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 85 | target.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 86 | } 87 | target.rx = e.clientX; 88 | target.ry = e.clientY; 89 | } 90 | 91 | 92 | function resize() { 93 | width = window.innerWidth; 94 | height = window.innerHeight; 95 | canvas.width = width; 96 | canvas.height = height; 97 | if(this.reqId) window.cancelAnimationFrame(this.reqId); 98 | initMap(); 99 | initAnimation(); 100 | } 101 | 102 | // animation 103 | function initAnimation() { 104 | if (this.reqId) { 105 | polyFill(this.reqId); 106 | } 107 | 108 | 109 | if(options.type == "heart-beat") { 110 | setInterval(heartBeat, options.heartBeatCD); 111 | } else if (options.type == "random-move") { 112 | animate(); 113 | for (var i = 0; i < points.length; i++) { 114 | shiftPoint(points[i]); 115 | } 116 | } 117 | } 118 | 119 | function animate() { 120 | 121 | ctx.clearRect(0, 0, width, height); 122 | 123 | if(options.type == "heart-beat") { 124 | var count = 0; 125 | for (var i = 0; i < intersections.length; i++) { 126 | var intersection = intersections[i]; 127 | if (intersection.circle.active > 0) { 128 | intersection.circle.active -= 0.012; 129 | intersection.circle.draw(); 130 | } else count++; 131 | } 132 | if(intersections.length > 0 && count == intersections.length) { 133 | intersections = []; 134 | return; 135 | } 136 | } else if (options.type == "random-move") { 137 | 138 | var rp = getRelativeP(); 139 | 140 | for (var i = 0; i < points.length; i++) { 141 | // detect points in range 142 | if (Math.abs(getDistance(rp, points[i])) < 4000) { 143 | points[i].active = 0.3; 144 | points[i].circle.active = 0.6; 145 | } else if (Math.abs(getDistance(rp, points[i])) < 20000) { 146 | points[i].active = 0.1; 147 | points[i].circle.active = 0.3; 148 | } else if (Math.abs(getDistance(rp, points[i])) < 40000) { 149 | points[i].active = 0.02; 150 | points[i].circle.active = 0.1; 151 | } else { 152 | points[i].active = 0; 153 | points[i].circle.active = 0; 154 | } 155 | drawLines(points[i]); 156 | points[i].circle.draw(); 157 | } 158 | } 159 | this.reqId = requestAnimationFrame(animate); 160 | } 161 | 162 | 163 | function heartBeat() { 164 | animate(); 165 | var clsP = findClosest(); 166 | var srcCircle = new Circle(clsP, 0); 167 | var activeTime = 3000 * 0.8; 168 | var _frames = activeTime * 60 / 1000; 169 | var step = options.heartBeatRange / _frames; 170 | var sleep = activeTime / _frames; 171 | var originOpacity = 0.8; 172 | var centerP = getRelativeP(); 173 | intersections = []; 174 | 175 | var f = function () { 176 | if (srcCircle.radius < options.heartBeatRange) { 177 | for (var i = 0; i < points.length; i++) { 178 | var curP = points[i]; 179 | if (getDistance(curP, srcCircle.pos) < Math.pow(srcCircle.radius, 2)) { 180 | for (var j = 0; j < curP.closest.length; j++) { 181 | var clsP = curP.closest[j]; 182 | var intersection = getIntersection(curP, clsP, srcCircle); 183 | if (intersection != undefined) { 184 | intersection.circle = new Circle(intersection, 1.2, centerP); 185 | intersection.circle.active = originOpacity; 186 | originOpacity *= 0.999; 187 | intersections.push(intersection); 188 | } 189 | } 190 | } 191 | } 192 | setTimeout(f, sleep); 193 | srcCircle.radius += step; 194 | } 195 | }; 196 | if (draw) f(); 197 | } 198 | 199 | function shiftPoint(p) { 200 | TweenLite.to(p, 1 + Math.random(), { 201 | x: p.originX - 50 + Math.random() * 100, 202 | y: p.originY - 50 + Math.random() * 100, 203 | onComplete: function () { 204 | shiftPoint(p); 205 | } 206 | }); 207 | } 208 | 209 | function findClosest() { 210 | var closestP = {x: -100, y: -100}; 211 | var rp = getRelativeP(); 212 | for (var i = 0; i < points.length; i++) { 213 | var curP = points[i]; 214 | closestP = getDistance(rp, curP) < getDistance(rp, closestP) ? 215 | curP : closestP; 216 | } 217 | return closestP; 218 | } 219 | 220 | 221 | function getNeighborPoint(p, type) { 222 | var deltaY = options.lineLen * Math.sin(60 * Math.PI / 180); 223 | var deltaX = options.lineLen * Math.cos(60 * Math.PI / 180); 224 | var res = {closest: []}; 225 | 226 | if (type == "left" || type == "right") { 227 | res.x = p.x + options.lineLen * (type == "left" ? -1 : 1); 228 | res.y = p.y; 229 | } else if (type == "rightTop" || type == "rightBottom") { 230 | res.x = p.x + deltaX; 231 | res.y = p.y + deltaY * (type == "rightTop" ? -1 : 1) 232 | } else if (type == "leftTop" || type == "leftBottom") { 233 | res.x = p.x - deltaX; 234 | res.y = p.y + deltaY * (type == "leftTop" ? -1 : 1) 235 | } 236 | res.type = type; 237 | p.closest.push(res); 238 | res.closest.push(p); 239 | return res; 240 | } 241 | 242 | 243 | // equation 244 | function getIntersection(p1, p2, circle) { 245 | var d1 = getDistance(p1, circle.pos); 246 | var d2 = getDistance(p2, circle.pos); 247 | var maxDis = Math.sqrt(Math.max(d1, d2)); 248 | var minDis = Math.sqrt(Math.min(d1, d2)); 249 | if (minDis < circle.radius && maxDis > circle.radius) { 250 | var k = (p1.y - p2.y) / (p1.x - p2.x); 251 | var b = p1.y - k * p1.x; 252 | var c = -circle.pos.x; 253 | var d = -circle.pos.y; 254 | var r = circle.radius; 255 | 256 | var delta = (Math.pow(k, 2) + 1) * Math.pow(r, 2) - Math.pow(c * k, 2) + 2 * (c * d + b * c) * k - Math.pow(d + b, 2); 257 | var candidateX1 = (-1 * Math.sqrt(delta) - k * (d + b) - c) / (Math.pow(k, 2) + 1); 258 | var candidateX2 = (Math.sqrt(delta) - k * (d + b) - c) / (Math.pow(k, 2) + 1); 259 | 260 | var candidateX = (candidateX1 < Math.max(p1.x, p2.x) && candidateX1 > Math.min(p1.x, p2.x)) 261 | ? candidateX1 : candidateX2; 262 | var candidateY = k * candidateX + b; 263 | return {x: candidateX, y: candidateY}; 264 | } 265 | 266 | return undefined; 267 | } 268 | 269 | function Circle(pos, rad, centerP) { 270 | 271 | this.pos = pos || null; 272 | this.radius = rad || null; 273 | this.centerP = centerP; 274 | 275 | this.draw = function () { 276 | if (!this.active) return; 277 | ctx.beginPath(); 278 | ctx.arc(this.pos.x, this.pos.y, this.radius, 0, 2 * Math.PI, false); 279 | var rgbRes = typeof options.rgb == "function" ? options.rgb(this.pos, this.centerP || this.pos) : options.rgb; 280 | rgbRes = "".concat(rgbRes.r).concat(",").concat(rgbRes.g).concat(",").concat(rgbRes.b); 281 | ctx.fillStyle = "rgba(" + rgbRes + "," + this.active + ")"; 282 | ctx.fill(); 283 | }; 284 | 285 | } 286 | 287 | // Canvas manipulation 288 | function drawLines(p) { 289 | if (!p.active) return; 290 | for (var i = 0; i < p.closest.length; i++) { 291 | ctx.beginPath(); 292 | ctx.moveTo(p.x, p.y); 293 | ctx.lineTo(p.closest[i].x, p.closest[i].y); 294 | var rgbRes = typeof options.rgb == "function" ? options.rgb(target) : options.rgb; 295 | rgbRes = "".concat(rgbRes.r).concat(",").concat(rgbRes.g).concat(",").concat(rgbRes.b); 296 | ctx.strokeStyle = "rgba(" + rgbRes + "," + p.active + ")"; 297 | ctx.stroke(); 298 | } 299 | } 300 | 301 | // Util 302 | function getDistance(p1, p2) { 303 | return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2); 304 | } 305 | 306 | function createMap() { 307 | 308 | if(options.type == "random-move") { 309 | 310 | points = []; 311 | 312 | for (var x = 0; x < width; x = x + width / 20) { 313 | for (var y = 0; y < height; y = y + height / 20) { 314 | var px = x + Math.random() * width / 20; 315 | var py = y + Math.random() * height / 20; 316 | var p = {x: px, originX: px, y: py, originY: py}; 317 | points.push(p); 318 | } 319 | } 320 | 321 | // for each point find the 5 closest points 322 | for (var i = 0; i < points.length; i++) { 323 | var closest = []; 324 | var p1 = points[i]; 325 | for (var j = 0; j < points.length; j++) { 326 | var p2 = points[j]; 327 | if (!(p1 == p2)) { 328 | var placed = false; 329 | for (var k = 0; k < 5; k++) { 330 | if (!placed) { 331 | if (closest[k] == undefined) { 332 | closest[k] = p2; 333 | placed = true; 334 | } 335 | } 336 | } 337 | 338 | for (var k = 0; k < 5; k++) { 339 | if (!placed) { 340 | if (getDistance(p1, p2) < getDistance(p1, closest[k])) { 341 | closest[k] = p2; 342 | placed = true; 343 | } 344 | } 345 | } 346 | } 347 | } 348 | p1.closest = closest; 349 | } 350 | 351 | // assign a circle to each point 352 | for (var i = 0; i < points.length; i++) { 353 | points[i].circle = new Circle(points[i], 2 + Math.random() * 2, undefined); 354 | } 355 | 356 | } else if (options.type == "heart-beat") { 357 | 358 | var source = {x: width / 2, y: height / 2, closest: []}; 359 | var pointsQueue = [ 360 | getNeighborPoint(source, "left"), 361 | getNeighborPoint(source, "rightTop"), 362 | getNeighborPoint(source, "rightBottom") 363 | ]; 364 | 365 | // create points 366 | points = [source]; 367 | 368 | for (; pointsQueue.length > 0;) { 369 | 370 | var p = pointsQueue.pop(); 371 | if (0 < p.x && p.x < width && 0 < p.y && p.y < height) { 372 | var same = false; 373 | for (var i = 0; i < points.length; i++) { 374 | var savedP = points[i]; 375 | var distance = getDistance(p, savedP); 376 | 377 | if (distance < Math.pow(options.lineLen, 2) * 0.1) { 378 | same = true; 379 | break; 380 | } 381 | } 382 | if (!same) { 383 | points.push(p); 384 | var type = p.type; 385 | if (type == "leftTop" || type == "leftBottom") { 386 | pointsQueue.unshift(getNeighborPoint(p, "left")); 387 | pointsQueue.unshift(getNeighborPoint(p, type == "leftTop" ? "rightTop" : "rightBottom")); 388 | } else if (type == "rightTop" || type == "rightBottom") { 389 | pointsQueue.unshift(getNeighborPoint(p, "right")); 390 | pointsQueue.unshift(getNeighborPoint(p, type == "rightTop" ? "leftTop" : "leftBottom")); 391 | } else if (type == "left") { 392 | pointsQueue.unshift(getNeighborPoint(p, "leftBottom")); 393 | pointsQueue.unshift(getNeighborPoint(p, "leftTop")); 394 | } else if (type == "right") { 395 | pointsQueue.unshift(getNeighborPoint(p, "rightBottom")); 396 | pointsQueue.unshift(getNeighborPoint(p, "rightTop")); 397 | } 398 | } 399 | } 400 | } 401 | 402 | // assign a circle to each point 403 | for (var i = 0; i < points.length; i++) { 404 | points[i].circle = new Circle(points[i], 2); 405 | } 406 | } 407 | 408 | } 409 | 410 | function getRelativeP() { 411 | var x = target.x - $canvas.offset().left; 412 | var y = target.y - $canvas.offset().top; 413 | return {x: x, y: y} 414 | } 415 | 416 | function polyFill(curReq) { 417 | var lastTime = 0; 418 | var vendors = ['ms', 'moz', 'webkit', 'o']; 419 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 420 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 421 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; 422 | } 423 | 424 | if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) { 425 | var currTime = new Date().getTime(); 426 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 427 | var id = window.setTimeout(function () { 428 | callback(currTime + timeToCall); 429 | }, timeToCall); 430 | lastTime = currTime + timeToCall; 431 | return id; 432 | }; 433 | if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { 434 | clearTimeout(id); 435 | }; 436 | 437 | if (curReq) { 438 | window.cancelAnimationFrame(curReq); 439 | } 440 | } 441 | } 442 | 443 | 444 | }; 445 | -------------------------------------------------------------------------------- /magic-canvas.min.js: -------------------------------------------------------------------------------- 1 | jQuery.magicCanvas={reqId:0,draw:function(a){function b(){v=document.getElementById("reactive-bg-canvas"),t=$(window).width(),u=$(window).height(),v.style.position="fixed",v.style.zIndex=D.zIndex,v.style.top="0px",v.style.left="0px",v.width=t,v.height=u,y={x:t/2,y:u/2,rx:t/2,ry:u/2},w=v.getContext("2d"),q()}function c(){"ontouchstart"in window||window.addEventListener("mousemove",e),window.addEventListener("scroll",d),window.addEventListener("resize",f)}function d(){y.x=y.rx+document.body.scrollLeft+document.documentElement.scrollLeft,y.y=y.ry+document.body.scrollTop+document.documentElement.scrollTop}function e(a){a.pageX||a.pageY?(y.x=a.pageX,y.y=a.pageY):(a.clientX||a.clientY)&&(y.x=a.clientX+document.body.scrollLeft+document.documentElement.scrollLeft,y.y=a.clientY+document.body.scrollTop+document.documentElement.scrollTop),y.rx=a.clientX,y.ry=a.clientY}function f(){t=window.innerWidth,u=window.innerHeight,v.width=t,v.height=u,this.reqId&&window.cancelAnimationFrame(this.reqId),b(),g()}function g(){if(this.reqId&&s(this.reqId),"heart-beat"==D.type)setInterval(i,D.heartBeatCD);else if("random-move"==D.type){h();for(var a=0;a0?(c.circle.active-=.012,c.circle.draw()):a++}if(A.length>0&&a==A.length)return void(A=[])}else if("random-move"==D.type)for(var d=r(),b=0;bc.radius){var h=(a.y-b.y)/(a.x-b.x),i=a.y-h*a.x,j=-c.pos.x,k=-c.pos.y,l=c.radius,m=(Math.pow(h,2)+1)*Math.pow(l,2)-Math.pow(j*h,2)+2*(j*k+i*j)*h-Math.pow(k+i,2),n=(-1*Math.sqrt(m)-h*(k+i)-j)/(Math.pow(h,2)+1),o=(Math.sqrt(m)-h*(k+i)-j)/(Math.pow(h,2)+1),q=nMath.min(a.x,b.x)?n:o,r=h*q+i;return{x:q,y:r}}}function n(a,b,c){this.pos=a||null,this.radius=b||null,this.centerP=c,this.draw=function(){if(this.active){w.beginPath(),w.arc(this.pos.x,this.pos.y,this.radius,0,2*Math.PI,!1);var a="function"==typeof D.rgb?D.rgb(this.pos,this.centerP||this.pos):D.rgb;a="".concat(a.r).concat(",").concat(a.g).concat(",").concat(a.b),w.fillStyle="rgba("+a+","+this.active+")",w.fill()}}}function o(a){if(a.active)for(var b=0;ba;a+=t/20)for(var b=0;u>b;b+=u/20){var c=a+Math.random()*t/20,d=b+Math.random()*u/20,e={x:c,originX:c,y:d,originY:d};x.push(e)}for(var f=0;fm;m++)k||void 0==g[m]&&(g[m]=j,k=!0);for(var m=0;5>m;m++)k||p(h,j)0;){var e=q.pop();if(0", 5 | "version": "1.0.0", 6 | "homepage": "http://decaywood.github.io", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/decaywood/decaywood.github.io" 11 | }, 12 | "bugs": "https://github.com/decaywood/decaywood.github.io/issues", 13 | "devDependencies": { 14 | "grunt": "~0.4.5", 15 | "grunt-banner": "~0.2.3", 16 | "grunt-contrib-concat": "~0.5.1", 17 | "grunt-contrib-less": "~0.11.4", 18 | "grunt-contrib-uglify": "~0.5.1", 19 | "grunt-contrib-watch": "~0.6.1" 20 | } 21 | } 22 | --------------------------------------------------------------------------------