< html xmlns = "http://www.w3.org/1999/xhtml" > |
<--源码仅供学习,请勿用于商业用途--> |
< head > |
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
< title >atom monitor,extremely fun!</ title > |
< style > |
body { |
margin: 0; |
} |
canvas { |
display: block; |
position: absolute; |
top: 0; |
left: 0; |
cursor: crosshair; |
} |
#instructions { |
position: fixed; |
top: 0; |
left: 0; |
color: white; |
font-weight: 900; |
padding: 15px; |
font-family: 'Open Sans', sans-serif; |
background-color: #2fa1d6; |
transition: all .5s; |
transform: translate(0, 0); |
} |
#instructions.hide { |
transform: translate(0, -100%); |
} |
#instructions > i { |
margin-left: 9px; |
cursor: pointer; |
} |
</ style > |
</ head > |
< body > |
< script src = "script/main_jp.js" ></ script > |
< canvas id = "trails" ></ canvas > |
< canvas id = "particles" ></ canvas > |
< div id = "instructions" >点击任意位置移动原子核< i class = "fa fa-close" ></ i > |
</ div > |
< script > |
"use strict"; |
function _possibleConstructorReturn(self, call) { |
if (!self) { |
throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); |
} |
return call && (typeof call === "object" || typeof call === "function") ? call : self; |
} |
function _inherits(subClass, superClass) { |
if (typeof superClass !== "function" && superClass !== null) { |
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); |
} |
subClass.prototype = Object.create(superClass && superClass.prototype, { |
constructor: { |
value: subClass, |
enumerable: false, |
writable: true, |
configurable: true |
} |
}); |
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; |
} |
function _classCallCheck(instance, Constructor) { |
if (!(instance instanceof Constructor)) { |
throw new TypeError("Cannot call a class as a function"); |
} |
} |
//vector class |
var Vector = function() { |
function Vector() { |
var x = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0]; |
var y = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; |
_classCallCheck(this, Vector); |
this.x = x; |
this.y = y; |
this.magnitude = Math.sqrt(x * x + y * y); |
this.angle = Math.atan2(y, x); |
} |
Vector.prototype.add = function add(v) { |
this.x = this.x + v.x; |
this.y = this.y + v.y; |
this.magnitude = Math.sqrt(this.x * this.x + this.y * this.y); |
this.angle = Math.atan2(this.y, this.x); |
return this; |
}; |
Vector.prototype.subtract = function subtract(v) { |
this.x = this.x - v.x; |
this.y = this.y - v.y; |
this.magnitude = Math.sqrt(this.x * this.x + this.y * this.y); |
this.angle = Math.atan2(this.y, this.x); |
return this; |
}; |
Vector.prototype.setAngle = function setAngle(angle) { |
this.angle = angle; |
this.x = this.magnitude * Math.cos(angle); |
this.y = this.magnitude * Math.sin(angle); |
return this; |
}; |
Vector.prototype.setMagnitude = function setMagnitude(magnitude) { |
this.magnitude = magnitude; |
this.x = Math.cos(this.angle) * magnitude; |
this.y = Math.sin(this.angle) * magnitude; |
return this; |
}; |
return Vector; |
}(); |
//particle class |
var Particle = function() { |
function Particle(opts) { |
_classCallCheck(this, Particle); |
this.x = opts.x || Math.random() * cW; |
this.y = opts.y || Math.random() * cH; |
this.radius = opts.radius || 15; |
this.v = opts.v || new Vector(); |
this.acc = opts.acc || new Vector(); |
this.mass = opts.mass || 40; |
this.color = opts.color || 320; |
this.maxV = opts.maxV || 8; |
this.maxA = opts.maxA || 0.5; |
this.tasteTheRainbow = opts.tasteTheRainbow || false; |
if (opts.trail) { |
this.trail = true; |
this.trailLength = opts.trailLength || 10; |
this.trajPoints = new Queue([]); |
} |
} |
Particle.prototype.accelerate = function accelerate() { |
this.acc.magnitude = this.acc.magnitude > this.maxA ? this.acc.setMagnitude(this.maxA) : this.acc.magnitude; |
this.v.add(this.acc); |
}; |
Particle.prototype.isOnScreen = function isOnScreen() { |
return this.x <= cW || this.x >= 0 || this.y <= cH || this.y >= 0; |
}; |
Particle.prototype.update = function update() { |
if (this.acc.magnitude) { |
this.accelerate(); |
} |
if (this.trail) { |
var point = { |
x: this.x, |
y: this.y |
}; |
this.trajPoints.enqueue(point); |
if (this.trajPoints.getLength() >= this.trailLength) { |
this.trajPoints.dequeue(); |
} |
} |
this.v.magnitude = this.v.magnitude > this.maxV ? this.v.setMagnitude(this.maxV) : this.v.magnitude; |
this.x += this.v.x; |
this.y += this.v.y; |
if (this.tasteTheRainbow) { |
this.color = this.color <= 360 ? ++this.color : 1; |
} |
}; |
Particle.prototype.render = function render(context) { |
var trailContext = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; |
context.beginPath(); |
context.fillStyle = "hsl(" + this.color + ", 100%, 50%)"; |
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2); |
context.fill(); |
context.closePath(); |
if (this.trail && trailContext) { |
var trajectory = this.trajPoints; |
trailContext.beginPath(); |
trailContext.strokeStyle = "hsl(" + this.color + ", 100%, 50%)"; |
trailContext.lineWidth = 0.2; |
trailContext.moveTo(trajectory.queue[0].x, trajectory.queue[0].y); |
for (var i = 1, len = trajectory.getLength(); i < len ; i++) { |
trailContext.lineTo(trajectory.queue[i].x, trajectory.queue[i].y); |
} |
trailContext.stroke(); |
trailContext.closePath(); |
} |
}; |
return Particle; |
}(); |
var Planet = function (_Particle) { |
_inherits(Planet, _Particle); |
function Planet() { |
_classCallCheck(this, Planet); |
return _possibleConstructorReturn(this, _Particle.apply(this, arguments)); |
} |
Planet.prototype.gravitate = function gravitate(p) { |
if (Particle.prototype.isPrototypeOf(p)) { |
var d = Math .sqrt((this.x - p.x) * (this.x - p.x) + (this.y - p.y) * (this.y - p.y)); |
var attractiveForce = p .mass * this.mass / (d * d); |
this.acc.setAngle(Math.atan2(p.y - this.y, p.x - this.x)).setMagnitude(attractiveForce); |
} else { |
throw new Error("The argument passed to the gravitate function must be a particle"); |
} |
this.update(); |
}; |
Planet.prototype.gravitateStarCluster = function gravitateStarCluster(cluster) { |
var gV = new Vector(); |
for (var i = 0 ; i < cluster.length; i++) { |
var _star = cluster [i]; |
if (Particle.prototype.isPrototypeOf(_star)) { |
var v = new Vector(); |
var d = Math .sqrt((this.x - _star.x) * (this.x - _star.x) + (this.y - _star.y) * (this.y - _star.y)); |
var attractiveForce = _star .mass * this.mass / (d * d); |
v.setAngle(Math.atan2(_star.y - this.y, _star.x - this.x)).setMagnitude(attractiveForce); |
gV = gV.add(v); |
} else { |
throw new Error("The argument supplied to the gravitateStarCluster function must be an array of particles"); |
} |
} |
this.acc.setAngle(gV.angle).setMagnitude(gV.magnitude); |
this.update(); |
}; |
return Planet; |
}(Particle); |
var Queue = function () { |
function Queue(array) { |
_classCallCheck(this, Queue); |
this.queue = array ; |
} |
Queue.prototype.getLength = function getLength() { |
return this.queue.length; |
}; |
Queue.prototype.enqueue = function enqueue(element) { |
this.queue.unshift(element); |
}; |
Queue.prototype.dequeue = function dequeue() { |
this.queue.pop(); |
}; |
Queue.prototype.display = function display() { |
for (var i = 0 ; i < this.getLength; i++) { |
console.log(this.queue[i]); |
} |
}; |
return Queue; |
}(); |
//util function to paint entire canvas of specified color |
function paintCanvas(color, context) { |
var W = context .canvas.clientWidth; |
var H = context .canvas.clientHeight; |
context.save(); |
context.fillStyle = color ; |
context.fillRect(0, 0, W, H); |
context.restore(); |
} |
//util function that returns a random number in a given range |
function randomInRange(min, max) { |
var result = min + Math.random() * (max - min); |
return result; |
} |
////////////////////////////////////// |
// -- THIS ANIMATION'S VARIABLES -- // |
////////////////////////////////////// |
//canvas |
var trailCanvas = document .getElementById('trails'); |
var particlesCanvas = document .getElementById('particles'); |
var trailCtx = trailCanvas .getContext('2d'); |
var particleCtx = particlesCanvas .getContext('2d'); |
var cW = particlesCanvas .width = trailCanvas .width = window .innerWidth; |
var cH = particlesCanvas .height = trailCanvas .height = window .innerHeight; |
//animation constants |
var settings = { |
STAR_MASS: 1500, |
PLANET_MASS: 20, |
PLANET_V_X: 2, |
P_TRAIL: true, |
P_MAX_VELOCITY: 8, |
P_MAX_ACC: 0.5, |
P_MIN_VELOCITY: 5, |
PARTICLE_NUM: 70, |
BOUNDS: false, |
TRAIL_LENGTH: 90, |
TRAIL_CNVS: trailCanvas, |
PARTICLE_CNVS: particlesCanvas, |
COLOR: 320, |
TRAIL_CTXT: trailCtx, |
TASTETHERAINBOW: true, |
PARTICLE_CTXT: particleCtx |
}; |
window.addEventListener('resize', function() { |
cW = particlesCanvas .width = trailCanvas .width = window .innerWidth; |
cH = particlesCanvas .height = trailCanvas .height = window .innerHeight; |
}); |
//mouse events and stuff |
var mX = -1; |
var mY = -1; |
var draggingStar = false ; |
document.addEventListener('mousemove', function(e) { |
mX = e .clientX; |
mY = e .clientY; |
}); |
settings.PARTICLE_CNVS.addEventListener('click', function() { |
draggingStar = !draggingStar; |
}); |
//stars and particles |
var s = []; |
var p = []; |
var star = new Particle({ |
x: cW / 2, |
y: cH / 2, |
radius: 15, |
color: settings.COLOR, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
mass: settings.STAR_MASS |
}); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
var planet = new Planet({ |
x: Math.random() * cW, |
y: Math.random() * cH, |
radius: 2, |
mass: settings.PLANET_MASS, |
trail: settings.P_TRAIL, |
trailLength: settings.TRAIL_LENGTH, |
color: settings.COLOR, |
maxV: settings.P_MAX_VELOCITY, |
maxA: settings.P_MAX_ACC, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
v: new Vector(Math.random() < 0.5 ? -settings.P_MIN_VELOCITY : settings.P_MIN_VELOCITY, 0) |
}); |
p.push(planet); |
} |
//animation function |
function animate() { |
settings.PARTICLE_CTXT.clearRect(0, 0, cW, cH); |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
paintCanvas('black', settings.TRAIL_CTXT); |
star.update(); |
star.render(settings.PARTICLE_CTXT); |
for (var i = 0 ; i < p.length; i++) { |
p[i].gravitate(star); |
if (settings.BOUNDS) { |
if (p[i].x > cW) { |
p[i].x = cW; |
} |
if (p[i].x < 0 ) { |
p[i] .x = 0 ; |
} |
if (p[i].y > cH) { |
p[i].y = cH; |
} |
if (p[i].y < 0 ) { |
p[i] .y = 0 ; |
} |
} |
if (p[i].isOnScreen()) { |
p[i].render(settings.PARTICLE_CTXT, settings.TRAIL_CTXT); |
} |
} |
if (draggingStar) { |
star.x += (mX - star.x) * 0.1; |
star.y += (mY - star.y) * 0.1; |
} |
requestAnimationFrame(animate); |
} |
//start loop! |
animate(); |
//datgui thangs |
var gui = new dat.GUI(); |
gui.add(settings, 'STAR_MASS', 500, 10000).name('star mass').onFinishChange(function() { |
star.mass = settings .STAR_MASS; |
}); |
gui.add(settings, 'P_TRAIL').name('particle trail').onFinishChange(function() { |
p = []; |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
var planet = new Planet({ |
x: Math.random() * cW, |
y: Math.random() * cH, |
radius: 2, |
mass: settings.PLANET_MASS, |
trail: settings.P_TRAIL, |
trailLength: settings.TRAIL_LENGTH, |
color: settings.COLOR, |
maxV: settings.P_MAX_VELOCITY, |
maxA: settings.P_MAX_ACC, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
v: new Vector(Math.random() < 0.5 ? -settings.P_MIN_VELOCITY : settings.P_MIN_VELOCITY, 0) |
}); |
p.push(planet); |
} |
star.color = settings .COLOR; |
}); |
gui.add(settings, 'P_MAX_VELOCITY', 4, 14).name('max velocity').onFinishChange(function() { |
p = []; |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
var planet = new Planet({ |
x: Math.random() * cW, |
y: Math.random() * cH, |
radius: 2, |
mass: settings.PLANET_MASS, |
trail: settings.P_TRAIL, |
trailLength: settings.TRAIL_LENGTH, |
color: settings.COLOR, |
maxV: settings.P_MAX_VELOCITY, |
maxA: settings.P_MAX_ACC, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
v: new Vector(Math.random() < 0.5 ? -settings.P_MIN_VELOCITY : settings.P_MIN_VELOCITY, 0) |
}); |
p.push(planet); |
} |
star.color = settings .COLOR; |
}); |
gui.add(settings, 'P_MAX_ACC', 0.2, 2).name('max acceleration').onFinishChange(function() { |
p = []; |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
var planet = new Planet({ |
x: Math.random() * cW, |
y: Math.random() * cH, |
radius: 2, |
mass: settings.PLANET_MASS, |
trail: settings.P_TRAIL, |
trailLength: settings.TRAIL_LENGTH, |
color: settings.COLOR, |
maxV: settings.P_MAX_VELOCITY, |
maxA: settings.P_MAX_ACC, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
v: new Vector(Math.random() < 0.5 ? -settings.P_MIN_VELOCITY : settings.P_MIN_VELOCITY, 0) |
}); |
p.push(planet); |
} |
star.color = settings .COLOR; |
}); |
gui.add(settings, 'PARTICLE_NUM', 1, 250).name('particles number').onFinishChange(function() { |
p = []; |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
var planet = new Planet({ |
x: Math.random() * cW, |
y: Math.random() * cH, |
radius: 2, |
mass: settings.PLANET_MASS, |
trail: settings.P_TRAIL, |
trailLength: settings.TRAIL_LENGTH, |
color: settings.COLOR, |
maxV: settings.P_MAX_VELOCITY, |
maxA: settings.P_MAX_ACC, |
tasteTheRainbow: settings.TASTETHERAINBOW, |
v: new Vector(Math.random() < 0.5 ? -settings.P_MIN_VELOCITY : settings.P_MIN_VELOCITY, 0) |
}); |
p.push(planet); |
} |
star.color = settings .COLOR; |
}); |
gui.add(settings, 'BOUNDS').name('bounds'); |
gui.add(settings, 'TRAIL_LENGTH', 10, 200).name('trail length').onFinishChange(function() { |
settings.TRAIL_CTXT.clearRect(0, 0, cW, cH); |
for (var i = 0 ; i < settings.PARTICLE_NUM; i++) { |
p[i] .trajPoints = new Queue([]); |
p[i] .trailLength = settings .TRAIL_LENGTH; |
} |
}); |
//for debugging without printing stuff 1000000000 times in the console |
//window.setInterval(function(){ console.log(); }, 2000); |
//instructions panel script |
var instructionsPanel = document .querySelector('#instructions'); |
var closeButton = document .querySelector('#instructions > i'); |
closeButton.addEventListener('click', function() { |
instructionsPanel.classList.add('hide'); |
}); |
</ script > |
</ body > |
</ html > |