(function(window){ function random(min, max) { return min + Math.floor(Math.random() * (max - min + 1)); } function bezier(cp, t) { var p1 = cp[0].mul((1 - t) * (1 - t)); var p2 = cp[1].mul(2 * t * (1 - t)); var p3 = cp[2].mul(t * t); return p1.add(p2).add(p3); } function inheart(x, y, r) { // x^2+(y-(x^2)^(1/3))^2 = 1 // http://www.wolframalpha.com/input/?i=x%5E2%2B%28y-%28x%5E2%29%5E%281%2F3%29%29%5E2+%3D+1 var z = ((x / r) * (x / r) + (y / r) * (y / r) - 1) * ((x / r) * (x / r) + (y / r) * (y / r) - 1) * ((x / r) * (x / r) + (y / r) * (y / r) - 1) - (x / r) * (x / r) * (y / r) * (y / r) * (y / r); return z < 0; } Point = function(x, y) { this.x = x || 0; this.y = y || 0; } Point.prototype = { clone: function() { return new Point(this.x, this.y); }, add: function(o) { p = this.clone(); p.x += o.x; p.y += o.y; return p; }, sub: function(o) { p = this.clone(); p.x -= o.x; p.y -= o.y; return p; }, div: function(n) { p = this.clone(); p.x /= n; p.y /= n; return p; }, mul: function(n) { p = this.clone(); p.x *= n; p.y *= n; return p; } } Heart = function() { // x = 16 sin^3 t // y = 13 cos t - 5 cos 2t - 2 cos 3t - cos 4t // http://www.wolframalpha.com/input/?i=x+%3D+16+sin%5E3+t%2C+y+%3D+(13+cos+t+-+5+cos+2t+-+2+cos+3t+-+cos+4t) var points = [], x, y, t; for (var i = 10; i < 30; i += 0.2) { t = i / Math.PI; x = 16 * Math.pow(Math.sin(t), 3); y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t); points.push(new Point(x, y)); } this.points = points; this.length = points.length; } Heart.prototype = { get: function(i, scale) { return this.points[i].mul(scale || 1); } } Seed = function(tree, point, scale, color) { this.tree = tree; var scale = scale || 1 var color = color || '#FF0000'; this.heart = { point : point, scale : scale, color : color, figure : new Heart(), } this.cirle = { point : point, scale : scale, color : color, radius : 5, } } Seed.prototype = { draw: function() { this.drawHeart(); this.drawText(); }, addPosition: function(x, y) { this.cirle.point = this.cirle.point.add(new Point(x, y)); }, canMove: function() { return this.cirle.point.y < (this.tree.height + 20); }, move: function(x, y) { this.clear(); this.drawCirle(); this.addPosition(x, y); }, canScale: function() { return this.heart.scale > 0.2; }, setHeartScale: function(scale) { this.heart.scale *= scale; }, scale: function(scale) { this.clear(); this.drawCirle(); this.drawHeart(); this.setHeartScale(scale); }, drawHeart: function() { var ctx = this.tree.ctx, heart = this.heart; var point = heart.point, color = heart.color, scale = heart.scale; ctx.save(); ctx.fillStyle = color; ctx.translate(point.x, point.y); ctx.beginPath(); ctx.moveTo(0, 0); for (var i = 0; i < heart.figure.length; i++) { var p = heart.figure.get(i, scale); ctx.lineTo(p.x, -p.y); } ctx.closePath(); ctx.fill(); ctx.restore(); }, drawCirle: function() { var ctx = this.tree.ctx, cirle = this.cirle; var point = cirle.point, color = cirle.color, scale = cirle.scale, radius = cirle.radius; ctx.save(); ctx.fillStyle = color; ctx.translate(point.x, point.y); ctx.scale(scale, scale); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, radius, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.restore(); }, drawText: function() { var ctx = this.tree.ctx, heart = this.heart; var point = heart.point, color = heart.color, scale = heart.scale; ctx.save(); ctx.strokeStyle = color; ctx.fillStyle = color; ctx.translate(point.x, point.y); ctx.scale(scale, scale); ctx.moveTo(0, 0); ctx.lineTo(15, 15); ctx.lineTo(60, 15); ctx.stroke(); ctx.moveTo(0, 0); ctx.scale(0.75, 0.75); ctx.font = "12px 微软雅黑,Verdana"; // 字号肿么没有用? (ˉ(∞)ˉ) ctx.fillText("click here", 23, 16); ctx.restore(); }, clear: function() { var ctx = this.tree.ctx, cirle = this.cirle; var point = cirle.point, scale = cirle.scale, radius = 26; var w = h = (radius * scale); ctx.clearRect(point.x - w, point.y - h, 4 * w, 4 * h); }, hover: function(x, y) { var ctx = this.tree.ctx; var pixel = ctx.getImageData(x, y, 1, 1); return pixel.data[3] == 255 } } Footer = function(tree, width, height, speed) { this.tree = tree; this.point = new Point(tree.seed.heart.point.x, tree.height - height / 2); this.width = width; this.height = height; this.speed = speed || 2; this.length = 0; } Footer.prototype = { draw: function() { var ctx = this.tree.ctx, point = this.point; var len = this.length / 2; ctx.save(); ctx.strokeStyle = 'rgb(35, 31, 32)'; ctx.lineWidth = this.height; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.translate(point.x, point.y); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(len, 0); ctx.lineTo(-len, 0); ctx.stroke(); ctx.restore(); if (this.length < this.width) { this.length += this.speed; } } } Tree = function(canvas, width, height, opt) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.width = width; this.height = height; this.opt = opt || {}; this.record = {}; this.initSeed(); this.initFooter(); this.initBranch(); this.initBloom(); } Tree.prototype = { initSeed: function() { var seed = this.opt.seed || {}; var x = seed.x || this.width / 2; var y = seed.y || this.height / 2; var point = new Point(x, y); var color = seed.color || '#FF0000'; var scale = seed.scale || 1; this.seed = new Seed(this, point, scale, color); }, initFooter: function() { var footer = this.opt.footer || {}; var width = footer.width || this.width; var height = footer.height || 5; var speed = footer.speed || 2; this.footer = new Footer(this, width, height, speed); }, initBranch: function() { var branchs = this.opt.branch || [] this.branchs = []; this.addBranchs(branchs); }, initBloom: function() { var bloom = this.opt.bloom || {}; var cache = [], num = bloom.num || 500, width = bloom.width || this.width, height = bloom.height || this.height, figure = this.seed.heart.figure; var r = 240, x, y; for (var i = 0; i < num; i++) { cache.push(this.createBloom(width, height, r, figure)); } this.blooms = []; this.bloomsCache = cache; }, toDataURL: function(type) { return this.canvas.toDataURL(type); }, draw: function(k) { var s = this, ctx = s.ctx; var rec = s.record[k]; if (!rec) { return ; } var point = rec.point, image = rec.image; ctx.save(); ctx.putImageData(image, point.x, point.y); ctx.restore(); }, addBranch: function(branch) { this.branchs.push(branch); }, addBranchs: function(branchs){ var s = this, b, p1, p2, p3, r, l, c; for (var i = 0; i < branchs.length; i++) { b = branchs[i]; p1 = new Point(b[0], b[1]); p2 = new Point(b[2], b[3]); p3 = new Point(b[4], b[5]); r = b[6]; l = b[7]; c = b[8] s.addBranch(new Branch(s, p1, p2, p3, r, l, c)); } }, removeBranch: function(branch) { var branchs = this.branchs; for (var i = 0; i < branchs.length; i++) { if (branchs[i] === branch) { branchs.splice(i, 1); } } }, canGrow: function() { return !!this.branchs.length; }, grow: function() { var branchs = this.branchs; for (var i = 0; i < branchs.length; i++) { var branch = branchs[i]; if (branch) { branch.grow(); } } }, addBloom: function (bloom) { this.blooms.push(bloom); }, removeBloom: function (bloom) { var blooms = this.blooms; for (var i = 0; i < blooms.length; i++) { if (blooms[i] === bloom) { blooms.splice(i, 1); } } }, createBloom: function(width, height, radius, figure, color, alpha, angle, scale, place, speed) { var x, y; while (true) { x = random(20, width - 20); y = random(20, height - 20); if (inheart(x - width / 2, height - (height - 40) / 2 - y, radius)) { return new Bloom(this, new Point(x, y), figure, color, alpha, angle, scale, place, speed); } } }, canFlower: function() { return !!this.blooms.length; }, flower: function(num) { var s = this, blooms = s.bloomsCache.splice(0, num); for (var i = 0; i < blooms.length; i++) { s.addBloom(blooms[i]); } blooms = s.blooms; for (var j = 0; j < blooms.length; j++) { blooms[j].flower(); } }, snapshot: function(k, x, y, width, height) { var ctx = this.ctx; var image = ctx.getImageData(x, y, width, height); this.record[k] = { image: image, point: new Point(x, y), width: width, height: height } }, setSpeed: function(k, speed) { this.record[k || "move"].speed = speed; }, move: function(k, x, y) { var s = this, ctx = s.ctx; var rec = s.record[k || "move"]; var point = rec.point, image = rec.image, speed = rec.speed || 10, width = rec.width, height = rec.height; i = point.x + speed < x ? point.x + speed : x; j = point.y + speed < y ? point.y + speed : y; ctx.save(); ctx.clearRect(point.x, point.y, width, height); ctx.putImageData(image, i, j); ctx.restore(); rec.point = new Point(i, j); rec.speed = speed * 0.95; if (rec.speed < 2) { rec.speed = 2; } return i < x || j < y; }, jump: function() { var s = this, blooms = s.blooms; if (blooms.length) { for (var i = 0; i < blooms.length; i++) { blooms[i].jump(); } } if ((blooms.length && blooms.length < 3) || !blooms.length) { var bloom = this.opt.bloom || {}, width = bloom.width || this.width, height = bloom.height || this.height, figure = this.seed.heart.figure; var r = 240, x, y; for (var i = 0; i < random(1,2); i++) { blooms.push(this.createBloom(width / 2 + width, height, r, figure, null, 1, null, 1, new Point(random(-100,600), 720), random(200,300))); } } } } Branch = function(tree, point1, point2, point3, radius, length, branchs) { this.tree = tree; this.point1 = point1; this.point2 = point2; this.point3 = point3; this.radius = radius; this.length = length || 100; this.len = 0; this.t = 1 / (this.length - 1); this.branchs = branchs || []; } Branch.prototype = { grow: function() { var s = this, p; if (s.len <= s.length) { p = bezier([s.point1, s.point2, s.point3], s.len * s.t); s.draw(p); s.len += 1; s.radius *= 0.97; } else { s.tree.removeBranch(s); s.tree.addBranchs(s.branchs); } }, draw: function(p) { var s = this; var ctx = s.tree.ctx; ctx.save(); ctx.beginPath(); ctx.fillStyle = 'rgb(35, 31, 32)'; ctx.shadowColor = 'rgb(35, 31, 32)'; ctx.shadowBlur = 2; ctx.moveTo(p.x, p.y); ctx.arc(p.x, p.y, s.radius, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.restore(); } } Bloom = function(tree, point, figure, color, alpha, angle, scale, place, speed) { this.tree = tree; this.point = point; this.color = color || 'rgb(255,' + random(0, 255) + ',' + random(0, 255) + ')'; this.alpha = alpha || random(0.3, 1); this.angle = angle || random(0, 360); this.scale = scale || 0.1; this.place = place; this.speed = speed; this.figure = figure; } Bloom.prototype = { setFigure: function(figure) { this.figure = figure; }, flower: function() { var s = this; s.draw(); s.scale += 0.1; if (s.scale > 1) { s.tree.removeBloom(s); } }, draw: function() { var s = this, ctx = s.tree.ctx, figure = s.figure; ctx.save(); ctx.fillStyle = s.color; ctx.globalAlpha = s.alpha; ctx.translate(s.point.x, s.point.y); ctx.scale(s.scale, s.scale); ctx.rotate(s.angle); ctx.beginPath(); ctx.moveTo(0, 0); for (var i = 0; i < figure.length; i++) { var p = figure.get(i); ctx.lineTo(p.x, -p.y); } ctx.closePath(); ctx.fill(); ctx.restore(); }, jump: function() { var s = this, height = s.tree.height; if (s.point.x < -20 || s.point.y > height + 20) { s.tree.removeBloom(s); } else { s.draw(); s.point = s.place.sub(s.point).div(s.speed).add(s.point); s.angle += 0.05; s.speed -= 1; } } } window.random = random; window.bezier = bezier; window.Point = Point; window.Tree = Tree; })(window);