├── 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 |
--------------------------------------------------------------------------------