- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Gravity Points</title>
- <style type="text/css">
- body {
- font-family: Helvetica sans-serif;
- padding: 0;
- margin: 0;
- background-color: #222;
- overflow: hidden;
- -webkit-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
- canvas {
- position: absolute;
- top: 0;
- left: 0;
- }
- .info {
- position: absolute;
- top: 0;
- left: 0;
- padding: 5px 15px;
- color: #eee;
- font-size: 13px;
- background-color: rgba(0, 0, 0, .5);
- }
- </style>
- </head>
- <body>
- <canvas id="c"></canvas>
- <div class="info">Click to add gravity point.</div>
- <script src="http://cdn.bootcss.com/dat-gui/0.5.1/dat.gui.js"></script>
- <script>
- /**
- * requestAnimationFrame
- */
- window.requestAnimationFrame = (function(){
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function (callback) {
- window.setTimeout(callback, 1000 / 60);
- };
- })();
- /**
- * Vector
- */
- function Vector(x, y) {
- this.x = x || 0;
- this.y = y || 0;
- }
- Vector.add = function(a, b) {
- return new Vector(a.x + b.x, a.y + b.y);
- };
- Vector.sub = function(a, b) {
- return new Vector(a.x - b.x, a.y - b.y);
- };
- Vector.scale = function(v, s) {
- return v.clone().scale(s);
- };
- Vector.random = function() {
- return new Vector(
- Math.random() * 2 - 1,
- Math.random() * 2 - 1
- );
- };
- Vector.prototype = {
- set: function(x, y) {
- if (typeof x === 'object') {
- y = x.y;
- x = x.x;
- }
- this.x = x || 0;
- this.y = y || 0;
- return this;
- },
- add: function(v) {
- this.x += v.x;
- this.y += v.y;
- return this;
- },
- sub: function(v) {
- this.x -= v.x;
- this.y -= v.y;
- return this;
- },
- scale: function(s) {
- this.x *= s;
- this.y *= s;
- return this;
- },
- length: function() {
- return Math.sqrt(this.x * this.x + this.y * this.y);
- },
- lengthSq: function() {
- return this.x * this.x + this.y * this.y;
- },
- normalize: function() {
- var m = Math.sqrt(this.x * this.x + this.y * this.y);
- if (m) {
- this.x /= m;
- this.y /= m;
- }
- return this;
- },
- angle: function() {
- return Math.atan2(this.y, this.x);
- },
- angleTo: function(v) {
- var dx = v.x - this.x,
- dy = v.y - this.y;
- return Math.atan2(dy, dx);
- },
- distanceTo: function(v) {
- var dx = v.x - this.x,
- dy = v.y - this.y;
- return Math.sqrt(dx * dx + dy * dy);
- },
- distanceToSq: function(v) {
- var dx = v.x - this.x,
- dy = v.y - this.y;
- return dx * dx + dy * dy;
- },
- lerp: function(v, t) {
- this.x += (v.x - this.x) * t;
- this.y += (v.y - this.y) * t;
- return this;
- },
- clone: function() {
- return new Vector(this.x, this.y);
- },
- toString: function() {
- return '(x:' + this.x + ', y:' + this.y + ')';
- }
- };
- /**
- * GravityPoint
- */
- function GravityPoint(x, y, radius, targets) {
- Vector.call(this, x, y);
- this.radius = radius;
- this.currentRadius = radius * 0.5;
- this._targets = {
- particles: targets.particles || [],
- gravities: targets.gravities || []
- };
- this._speed = new Vector();
- }
- GravityPoint.RADIUS_LIMIT = 100;
- GravityPoint.interferenceToPoint = true;
- GravityPoint.prototype = (function(o) {
- var s = new Vector(0, 0), p;
- for (p in o) s[p] = o[p];
- return s;
- })({
- gravity: 0.05,
- isMouseOver: false,
- dragging: false,
- destroyed: false,
- _easeRadius: 0,
- _dragDistance: null,
- _collapsing: false,
- hitTest: function(p) {
- return this.distanceTo(p) <this.radius;
- },
- startDrag: function(dragStartPoint) {
- this._dragDistance = Vector.sub(dragStartPoint, this);
- this.dragging = true;
- },
- drag: function(dragToPoint) {
- this.x = dragToPoint.x - this._dragDistance.x;
- this.y = dragToPoint.y - this._dragDistance.y;
- },
- endDrag: function() {
- this._dragDistance = null;
- this.dragging = false;
- },
- addSpeed: function(d) {
- this._speed = this._speed.add(d);
- },
- collapse: function(e) {
- this.currentRadius *= 1.75;
- this._collapsing = true;
- },
- render: function(ctx) {
- if (this.destroyed) return;
- var particles = this._targets.particles,
- i, len;
- for (i = 0, len = particles.length; i <len; i++) {
- particles[i].addSpeed(Vector.sub(this, particles[i]).normalize().scale(this.gravity));
- }
- this._easeRadius = (this._easeRadius + (this.radius - this.currentRadius) * 0.07) * 0.95;
- this.currentRadius += this._easeRadius;
- if (this.currentRadius < 0) this.currentRadius = 0;
- if (this._collapsing) {
- this.radius *= 0.75;
- if (this.currentRadius < 1) this.destroyed = true;
- this._draw(ctx);
- return;
- }
- var gravities = this._targets.gravities,
- g, absorp,
- area = this.radius * this.radius * Math.PI, garea;
- for (i = 0, len = gravities.length; i < len; i++) {
- g = gravities[i];
- if (g === this || g.destroyed) continue;
- if (
- (this.currentRadius>= g.radius || this.dragging) &&
- this.distanceTo(g) <(this.currentRadius + g.radius) * 0.85
- ) {
- g.destroyed = true;
- this.gravity += g.gravity;
- absorp = Vector.sub(g, this).scale(g.radius / this.radius * 0.5);
- this.addSpeed(absorp);
- garea = g.radius * g.radius * Math.PI;
- this.currentRadius = Math.sqrt((area + garea * 3) / Math.PI);
- this.radius = Math.sqrt((area + garea) / Math.PI);
- }
- g.addSpeed(Vector.sub(this, g).normalize().scale(this.gravity));
- }
- if (GravityPoint.interferenceToPoint && !this.dragging)
- this.add(this._speed);
- this._speed = new Vector();
- if (this.currentRadius> GravityPoint.RADIUS_LIMIT) this.collapse();
- this._draw(ctx);
- },
- _draw: function(ctx) {
- var grd, r;
- ctx.save();
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.radius * 5, 0, Math.PI * 2, false);
- ctx.fillStyle = grd;
- ctx.fill();
- r = Math.random() * this.currentRadius * 0.7 + this.currentRadius * 0.3;
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.currentRadius, 0, Math.PI * 2, false);
- ctx.fillStyle = grd;
- ctx.fill();
- ctx.restore();
- }
- });
- /**
- * Particle
- */
- function Particle(x, y, radius) {
- Vector.call(this, x, y);
- this.radius = radius;
- this._latest = new Vector();
- this._speed = new Vector();
- }
- Particle.prototype = (function(o) {
- var s = new Vector(0, 0), p;
- for (p in o) s[p] = o[p];
- return s;
- })({
- addSpeed: function(d) {
- this._speed.add(d);
- },
- update: function() {
- if (this._speed.length()> 12) this._speed.normalize().scale(12);
- this._latest.set(this);
- this.add(this._speed);
- }
- // render: function(ctx) {
- // if (this._speed.length()> 12) this._speed.normalize().scale(12);
- // this._latest.set(this);
- // this.add(this._speed);
- // ctx.save();
- // ctx.fillStyle = ctx.strokeStyle = '#fff';
- // ctx.lineCap = ctx.lineJoin = 'round';
- // ctx.lineWidth = this.radius * 2;
- // ctx.beginPath();
- // ctx.moveTo(this.x, this.y);
- // ctx.lineTo(this._latest.x, this._latest.y);
- // ctx.stroke();
- // ctx.beginPath();
- // ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
- // ctx.fill();
- // ctx.restore();
- // }
- });
- // Initialize
- (function() {
- // Configs
- var BACKGROUND_COLOR = 'rgba(11, 51, 56, 1)',
- PARTICLE_RADIUS = 1,
- G_POINT_RADIUS = 10,
- G_POINT_RADIUS_LIMITS = 65;
- // Vars
- var canvas, context,
- bufferCvs, bufferCtx,
- screenWidth, screenHeight,
- mouse = new Vector(),
- gravities = [],
- particles = [],
- grad,
- gui, control;
- // Event Listeners
- function resize(e) {
- screenWidth = canvas.width = window.innerWidth;
- screenHeight = canvas.height = window.innerHeight;
- bufferCvs.width = screenWidth;
- bufferCvs.height = screenHeight;
- context = canvas.getContext('2d');
- bufferCtx = bufferCvs.getContext('2d');
- var cx = canvas.width * 0.5,
- cy = canvas.height * 0.5;
- grad.addColorStop(0, 'rgba(0, 0, 0, 0)');
- grad.addColorStop(1, 'rgba(0, 0, 0, 0.35)');
- }
- function mouseMove(e) {
- mouse.set(e.clientX, e.clientY);
- var i, g, hit = false;
- for (i = gravities.length - 1; i>= 0; i--) {
- g = gravities[i];
- if ((!hit && g.hitTest(mouse)) || g.dragging)
- g.isMouseOver = hit = true;
- else
- g.isMouseOver = false;
- }
- canvas.style.cursor = hit ? 'pointer' : 'default';
- }
- function mouseDown(e) {
- for (var i = gravities.length - 1; i>= 0; i--) {
- if (gravities[i].isMouseOver) {
- gravities[i].startDrag(mouse);
- return;
- }
- }
- gravities.push(new GravityPoint(e.clientX, e.clientY, G_POINT_RADIUS, {
- particles: particles,
- gravities: gravities
- }));
- }
- function mouseUp(e) {
- for (var i = 0, len = gravities.length; i <len; i++) {
- if (gravities[i].dragging) {
- gravities[i].endDrag();
- break;
- }
- }
- }
- function doubleClick(e) {
- for (var i = gravities.length - 1; i>= 0; i--) {
- if (gravities[i].isMouseOver) {
- gravities[i].collapse();
- break;
- }
- }
- }
- // Functions
- function addParticle(num) {
- var i, p;
- for (i = 0; i <num; i++) {
- p = new Particle(
- Math.floor(Math.random() * screenWidth - PARTICLE_RADIUS * 2) + 1 + PARTICLE_RADIUS,
- Math.floor(Math.random() * screenHeight - PARTICLE_RADIUS * 2) + 1 + PARTICLE_RADIUS,
- PARTICLE_RADIUS
- );
- p.addSpeed(Vector.random());
- particles.push(p);
- }
- }
- function removeParticle(num) {
- if (particles.length <num) num = particles.length;
- for (var i = 0; i < num; i++) {
- particles.pop();
- }
- }
- // GUI Control
- control = {
- particleNum: 50
- };
- // Init
- canvas = document.getElementById('c');
- bufferCvs = document.createElement('canvas');
- window.addEventListener('resize', resize, false);
- resize(null);
- addParticle(control.particleNum);
- canvas.addEventListener('mousemove', mouseMove, false);
- canvas.addEventListener('mousedown', mouseDown, false);
- canvas.addEventListener('mouseup', mouseUp, false);
- canvas.addEventListener('dblclick', doubleClick, false);
- // GUI
- gui = new dat.GUI();
- gui.add(control, 'particleNum', 0, 500).step(1).name('Particle Num').onChange(function() {
- var n = (control.particleNum | 0) - particles.length;
- if (n> 0)
- addParticle(n);
- else if (n <0)
- removeParticle(-n);
- });
- gui.add(GravityPoint, 'interferenceToPoint').name('Interference Between Point');
- gui.close();
- // Start Update
- var loop = function() {
- var i, len, g, p;
- context.save();
- context.fillStyle = BACKGROUND_COLOR;
- context.fillRect(0, 0, screenWidth, screenHeight);
- context.fillStyle = grad;
- context.fillRect(0, 0, screenWidth, screenHeight);
- context.restore();
- for (i = 0, len = gravities.length; i <len; i++) {
- g = gravities[i];
- if (g.dragging) g.drag(mouse);
- g.render(context);
- if (g.destroyed) {
- gravities.splice(i, 1);
- len--;
- i--;
- }
- }
- bufferCtx.save();
- bufferCtx.globalCompositeOperation = 'destination-out';
- bufferCtx.globalAlpha = 0.35;
- bufferCtx.restore();
- // 描画
- // for (i = 0, len = particles.length; i < len; i++) {
- // particles[i].render(bufferCtx);
- // }
- len = particles.length;
- bufferCtx.save();
- bufferCtx.fillStyle = bufferCtx.strokeStyle = '#fff';
- bufferCtx.lineCap = bufferCtx.lineJoin = 'round';
- bufferCtx.lineWidth = PARTICLE_RADIUS * 2;
- bufferCtx.beginPath();
- for (i = 0; i < len; i++) {
- p = particles[i];
- p.update();
- bufferCtx.moveTo(p.x, p.y);
- bufferCtx.lineTo(p._latest.x, p._latest.y);
- }
- bufferCtx.stroke();
- bufferCtx.beginPath();
- for (i = 0; i < len; i++) {
- p = particles[i];
- bufferCtx.moveTo(p.x, p.y);
- bufferCtx.arc(p.x, p.y, p.radius, 0, Math.PI * 2, false);
- }
- bufferCtx.fill();
- bufferCtx.restore();
- context.drawImage(bufferCvs, 0, 0);
- requestAnimationFrame(loop);
- };
- loop();
- })();
- </script>
- </body>
- </html>
来源: http://www.qdfuns.com/article/35376/a0a6704f698599e341378f6f59d47180.html