Sidebar UI all hooked up.

master
Ben Burlingham 8 years ago
parent 20a3a14f88
commit 466f0f400f
  1. 9
      css/controls.scss
  2. 13
      css/style.css
  3. 81
      js/animation1.js
  4. 515
      js/bundle.js
  5. 138
      js/controls.js
  6. 14
      js/enums.js
  7. 246
      js/particle.js

@ -22,11 +22,16 @@
width: 100%;
}
.controls-button {
border: 0;
.controls-animating {
cursor: pointer;
font-size: 18px;
line-height: 20px;
padding: 10px;
user-select: none;
[type=checkbox] {
display: none;
}
&:hover {
background: #fff;

@ -106,14 +106,17 @@ body {
cursor: pointer;
width: 100%; }
.controls-button {
border: 0;
.controls-animating {
cursor: pointer;
font-size: 18px;
padding: 10px; }
.controls-button:hover {
line-height: 20px;
padding: 10px;
user-select: none; }
.controls-animating [type=checkbox] {
display: none; }
.controls-animating:hover {
background: #fff; }
.controls-button:focus {
.controls-animating:focus {
outline: 0; }
.controls-bottom {

@ -2,41 +2,49 @@ import Rx, { Observable } from 'rxjs';
import Particle from './particle';
import Store from './store';
import Controls from './controls';
import { CONTROLS } from './enums';
function Animation1() {
this.options = {
animating: false,
count: 1,
animating: false
randomizeRadius: true,
randomizeRotation: true,
// showMovementCircle: false,
// showVIsionGrid: false,
speed: 4
};
this.container = document.getElementById('animation1');
this.bounds = this.container.getBoundingClientRect();
this.controls = new Controls(document.getElementById('controls1'), this.options);
const eventStack = this.controls.mount();
this.particles = [];
const controls = new Controls(document.getElementById('controls1'), this.options);
const eventStack = controls.mount();
eventStack.subscribe(this.subscriber.bind(this));
this.particles = [];
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
this.updateCount();
this.updateAnimating(true);
// TODO Change animal pic
// TODO Change animal pic OR - use particle ##???
// TODO show movement circle
// TODO cleanup movement circle
// TODO cleanup vision grid
// TODO add core UI
// TODO regen vision grid is option updated
// TODO expose particle ##
// TODO kill hover on slider, finalize control panel location
// TODO ANIM2 Show vision grid (including touches!)
};
Animation1.prototype.subscriber = function({ key, value }) {
switch(key) {
case 'count':
this.updateCount();
break;
case 'animating':
this.updateAnimating(!this.options.animating);
break;
case CONTROLS.ANIMATING: this.updateAnimating(value); break;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.RADIUS: this.updateRadius(value); break;
case CONTROLS.ROTATION: this.updateRotation(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
}
@ -44,29 +52,42 @@ Animation1.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation1.prototype.updateCount = function() {
while (this.particles.length >= this.options.count) {
Animation1.prototype.updateAnimating = function(isAnimating) {
this.options.animating = isAnimating;
if (isAnimating) {
const fps$ = Rx.Observable.interval(1000 / 32)
.takeWhile(_ => this.options.animating);
fps$.subscribe(this.nextFrame.bind(this));
}
}
Animation1.prototype.updateCount = function(count) {
while (this.particles.length >= count) {
const p = this.particles.pop();
this.container.removeChild(p.node);
}
while (this.particles.length < this.options.count) {
const p = new Particle(this.container, this.bounds);
while (this.particles.length < count) {
const p = new Particle(this.container, this.bounds, this.options);
this.particles.push(p);
}
};
}
Animation1.prototype.updateAnimating = function(animating) {
Object.assign(this.options, { animating });
this.controls.updateOptions(this.options);
Animation1.prototype.updateRadius = function(randomizeRadius) {
this.options.randomizeRadius = randomizeRadius;
this.particles.forEach(p => p.updateOptions({ randomizeRadius }));
}
if (animating) {
const fps$ = Rx.Observable.interval(1000 / 32)
.takeWhile(_ => this.options.animating)
.finally(() => { console.error("Stopped."); })
Animation1.prototype.updateRotation = function(randomizeRotation) {
this.options.randomizeRotation = randomizeRotation;
this.particles.forEach(p => p.updateOptions({ randomizeRotation }));
}
fps$.subscribe(this.nextFrame.bind(this));
}
};
Animation1.prototype.updateSpeed = function(speed) {
this.options.speed = speed;
this.particles.forEach(p => p.updateOptions({ speed }));
}
export default Animation1;

@ -3460,81 +3460,116 @@ var _rxjs = __webpack_require__(19);
var _rxjs2 = _interopRequireDefault(_rxjs);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function Controls(node, options) {
this.node = node;
this.options = options;
this.countCtrl = createCountControl();
this.speedCtrl = createSpeedControl();
this.radiusCtrl = createRadiusControl();
this.rotationCtrl = createRotationControl();
this.animatingCtrl = createAnimatingControl();
var _enums = __webpack_require__(76);
this.updateOptions(options);
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
Controls.prototype.updateOptions = function (_ref) {
function Controls(container, _ref) {
var animating = _ref.animating,
count = _ref.count,
randomizeRadius = _ref.randomizeRadius,
randomizeRotation = _ref.randomizeRotation,
speed = _ref.speed;
this.animatingCtrl.innerHTML = animating ? '&#9724; Stop' : '&#9654; Start';
this.nodes = {
animating: createAnimatingControl(animating),
count: createCountControl(count),
radius: createRadiusControl(randomizeRadius),
rotation: createRotationControl(randomizeRotation),
speed: createSpeedControl(speed)
};
container.appendChild(this.nodes.count);
container.appendChild(this.nodes.radius);
container.appendChild(this.nodes.rotation);
container.appendChild(this.nodes.speed);
container.appendChild(this.nodes.animating);
this.updateOptions({ key: _enums.CONTROLS.ANIMATING, value: animating });
this.updateOptions({ key: _enums.CONTROLS.COUNT, value: count });
this.updateOptions({ key: _enums.CONTROLS.RADIUS, value: randomizeRadius });
this.updateOptions({ key: _enums.CONTROLS.ROTATION, value: randomizeRotation });
this.updateOptions({ key: _enums.CONTROLS.SPEED, value: speed });
}
Controls.prototype.updateOptions = function (_ref2) {
var key = _ref2.key,
value = _ref2.value;
if (key === _enums.CONTROLS.COUNT) {
this.nodes.count.querySelector('span').innerHTML = value == 1 ? '1 particle' : value + ' particles';
}
if (key === _enums.CONTROLS.SPEED) {
this.nodes.speed.querySelector('span').innerHTML = 'Speed: ' + value;
}
if (key === _enums.CONTROLS.ANIMATING) {
this.nodes.animating.querySelector('span').innerHTML = value ? '&#9724; Stop' : '&#9654; Start';
}
};
Controls.prototype.mount = function (customNodes) {
this.node.appendChild(this.countCtrl);
this.node.appendChild(this.speedCtrl);
this.node.appendChild(this.radiusCtrl);
this.node.appendChild(this.rotationCtrl);
this.node.appendChild(this.animatingCtrl);
return _rxjs2.default.Observable.merge(_rxjs2.default.Observable.fromEvent(this.countCtrl, 'change').map(function (evt) {
return { key: 'count', value: evt.target.value * 1 };
}), _rxjs2.default.Observable.fromEvent(this.speedCtrl, 'change').map(function (evt) {
return { key: 'speed', value: evt.target.value * 1 };
}), _rxjs2.default.Observable.fromEvent(this.radiusCtrl, 'change').map(function (evt) {
return { key: 'radius', value: evt.target.checked };
}), _rxjs2.default.Observable.fromEvent(this.rotationCtrl, 'change').map(function (evt) {
return { key: 'rotation', value: evt.target.checked };
}), _rxjs2.default.Observable.fromEvent(this.animatingCtrl, 'click').map(function (evt) {
return { key: 'animating', value: null };
}));
var animatingStream = _rxjs2.default.Observable.fromEvent(this.nodes.animating, 'change').map(function (evt) {
return { key: _enums.CONTROLS.ANIMATING, value: evt.target.checked };
});
var countStream = _rxjs2.default.Observable.fromEvent(this.nodes.count, 'input').map(function (evt) {
return { key: _enums.CONTROLS.COUNT, value: evt.target.value * 1 };
});
var radiusStream = _rxjs2.default.Observable.fromEvent(this.nodes.radius, 'change').map(function (evt) {
return { key: _enums.CONTROLS.RADIUS, value: evt.target.checked };
});
var rotationStream = _rxjs2.default.Observable.fromEvent(this.nodes.rotation, 'change').map(function (evt) {
return { key: _enums.CONTROLS.ROTATION, value: evt.target.checked };
});
var speedStream = _rxjs2.default.Observable.fromEvent(this.nodes.speed, 'input').map(function (evt) {
return { key: _enums.CONTROLS.SPEED, value: evt.target.value * 1 };
});
animatingStream.subscribe(this.updateOptions.bind(this));
countStream.subscribe(this.updateOptions.bind(this));
radiusStream.subscribe(this.updateOptions.bind(this));
rotationStream.subscribe(this.updateOptions.bind(this));
speedStream.subscribe(this.updateOptions.bind(this));
return _rxjs2.default.Observable.merge(animatingStream, countStream, radiusStream, rotationStream, speedStream);
};
var createCountControl = function createCountControl() {
var createAnimatingControl = function createAnimatingControl(value) {
var label = document.createElement('label');
label.className = 'controls-label';
label.className = 'controls-animating';
var text = document.createElement('span');
text.innerHTML = 'Number of particles';
text.className = 'controls-label-text';
text.innerHTML = '...';
var slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.className = 'controls-slider';
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
label.appendChild(slider);
return label;
};
var createSpeedControl = function createSpeedControl() {
var createCountControl = function createCountControl(value) {
var label = document.createElement('label');
label.className = 'controls-label';
var text = document.createElement('span');
text.innerHTML = 'Animation speed';
text.innerHTML = '...';
text.className = 'controls-label-text';
var slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.max = 5;
slider.value = value;
slider.className = 'controls-slider';
label.appendChild(text);
@ -3543,7 +3578,7 @@ var createSpeedControl = function createSpeedControl() {
return label;
};
var createRadiusControl = function createRadiusControl() {
var createRadiusControl = function createRadiusControl(value) {
var label = document.createElement('label');
label.className = 'controls-label';
@ -3554,6 +3589,7 @@ var createRadiusControl = function createRadiusControl() {
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-label-checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
@ -3561,7 +3597,7 @@ var createRadiusControl = function createRadiusControl() {
return label;
};
var createRotationControl = function createRotationControl() {
var createRotationControl = function createRotationControl(value) {
var label = document.createElement('label');
label.className = 'controls-label';
@ -3572,6 +3608,7 @@ var createRotationControl = function createRotationControl() {
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-label-checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
@ -3579,11 +3616,24 @@ var createRotationControl = function createRotationControl() {
return label;
};
var createAnimatingControl = function createAnimatingControl() {
var button = document.createElement('button');
button.className = 'controls-button controls-bottom';
var createSpeedControl = function createSpeedControl(value) {
var label = document.createElement('label');
label.className = 'controls-label';
var text = document.createElement('span');
text.className = 'controls-label-text';
var slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.value = value;
slider.className = 'controls-slider';
label.appendChild(text);
label.appendChild(slider);
return button;
return label;
};
exports.default = Controls;
@ -6208,30 +6258,40 @@ var _controls = __webpack_require__(38);
var _controls2 = _interopRequireDefault(_controls);
var _enums = __webpack_require__(76);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function Animation1() {
this.options = {
animating: false,
count: 1,
animating: false
randomizeRadius: true,
randomizeRotation: true,
// showMovementCircle: false,
// showVIsionGrid: false,
speed: 4
};
this.container = document.getElementById('animation1');
this.bounds = this.container.getBoundingClientRect();
this.controls = new _controls2.default(document.getElementById('controls1'), this.options);
var eventStack = this.controls.mount();
this.particles = [];
var controls = new _controls2.default(document.getElementById('controls1'), this.options);
var eventStack = controls.mount();
eventStack.subscribe(this.subscriber.bind(this));
this.particles = [];
this.updateCount();
this.updateAnimating(true);
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
// TODO Change animal pic
// TODO Change animal pic OR - use particle ##???
// TODO show movement circle
// TODO cleanup movement circle
// TODO cleanup vision grid
// TODO add core UI
// TODO regen vision grid is option updated
// TODO expose particle ##
// TODO kill hover on slider, finalize control panel location
// TODO ANIM2 Show vision grid (including touches!)
};
@ -6241,12 +6301,16 @@ Animation1.prototype.subscriber = function (_ref) {
value = _ref.value;
switch (key) {
case 'count':
this.updateCount();
break;
case 'animating':
this.updateAnimating(!this.options.animating);
break;
case _enums.CONTROLS.ANIMATING:
this.updateAnimating(value);break;
case _enums.CONTROLS.COUNT:
this.updateCount(value);break;
case _enums.CONTROLS.RADIUS:
this.updateRadius(value);break;
case _enums.CONTROLS.ROTATION:
this.updateRotation(value);break;
case _enums.CONTROLS.SPEED:
this.updateSpeed(value);break;
}
};
@ -6256,33 +6320,51 @@ Animation1.prototype.nextFrame = function () {
});
};
Animation1.prototype.updateCount = function () {
while (this.particles.length >= this.options.count) {
Animation1.prototype.updateAnimating = function (isAnimating) {
var _this = this;
this.options.animating = isAnimating;
if (isAnimating) {
var fps$ = _rxjs2.default.Observable.interval(1000 / 32).takeWhile(function (_) {
return _this.options.animating;
});
fps$.subscribe(this.nextFrame.bind(this));
}
};
Animation1.prototype.updateCount = function (count) {
while (this.particles.length >= count) {
var p = this.particles.pop();
this.container.removeChild(p.node);
}
while (this.particles.length < this.options.count) {
var _p = new _particle2.default(this.container, this.bounds);
while (this.particles.length < count) {
var _p = new _particle2.default(this.container, this.bounds, this.options);
this.particles.push(_p);
}
};
Animation1.prototype.updateAnimating = function (animating) {
var _this = this;
Object.assign(this.options, { animating: animating });
this.controls.updateOptions(this.options);
Animation1.prototype.updateRadius = function (randomizeRadius) {
this.options.randomizeRadius = randomizeRadius;
this.particles.forEach(function (p) {
return p.updateOptions({ randomizeRadius: randomizeRadius });
});
};
if (animating) {
var fps$ = _rxjs2.default.Observable.interval(1000 / 32).takeWhile(function (_) {
return _this.options.animating;
}).finally(function () {
console.error("Stopped.");
});
Animation1.prototype.updateRotation = function (randomizeRotation) {
this.options.randomizeRotation = randomizeRotation;
this.particles.forEach(function (p) {
return p.updateOptions({ randomizeRotation: randomizeRotation });
});
};
fps$.subscribe(this.nextFrame.bind(this));
}
Animation1.prototype.updateSpeed = function (speed) {
this.options.speed = speed;
this.particles.forEach(function (p) {
return p.updateOptions({ speed: speed });
});
};
exports.default = Animation1;
@ -6329,6 +6411,20 @@ var RAD = {
t360: Math.PI * 2
};
var CONTROLS = {
ANIMATING: 'animating',
COUNT: 'count',
RADIUS: 'radius',
ROTATION: 'rotation',
SPEED: 'speed'
};
var IMAGES = {
SEAHORSE: 'seahorse'
};
exports.CONTROLS = CONTROLS;
exports.IMAGES = IMAGES;
exports.RAD = RAD;
/***/ }),
@ -6392,6 +6488,115 @@ var random = {
}
};
/* ===== Constructor ===== */
function Particle(container, bounds, options) {
var _this = this;
this.container = container;
this.bounds = bounds;
this.visionGridPoints = calculateVisionGridPoints();
this.node = document.createElement('div');
this.node.className = 'particle has-vision';
this.circle = document.createElement('div');
this.circle.className = 'particle-movement-circle';
this.container.appendChild(this.node);
this.container.appendChild(this.circle);
this.visionGridPoints.forEach(function (point) {
_this.container.appendChild(point.div);
});
this.options = Object.assign({
randomizeRadius: false,
randomizeRotation: false,
showMovementCircle: false,
showVIsionGrid: false,
speed: 4
}, options);
this.arc = {
r: random.num(100, 200),
t: random.num(0, _enums.RAD.t360),
x: random.num(0, bounds.width),
y: random.num(0, bounds.height)
};
this.particle = {
clockwise: random.bool(),
x: 0,
y: 0
};
this.interval = 0;
this.updateOptions(this.options);
this.nextFrame();
};
Particle.prototype.nextFrame = function () {
this.move();
repaintParticle(this.arc, this.node, this.particle);
if (this.options.showMovementCircle) {
repaintCircle(this.arc, this.circle);
}
if (this.options.showVisionGrid) {
repaintVisionGrid(this.arc, this.particle, this.visionGridPoints);
}
};
Particle.prototype.updateOptions = function (options) {
Object.assign(this.options, options);
// this.particleImage = options.image || IMAGES.SEAHORSE;
};
Particle.prototype.move = function () {
// Randomly change radius and rotation direction.
this.interval -= 1;
if (this.interval <= 0) {
this.interval = random.num(50, 100);
if (this.options.randomizeRadius === true) {
this.arc = moveArc(this.arc, random.num(100, 200));
if (random.bool() && this.options.randomizeRotation === true) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
}
}
}
// Ensure constant velocity and theta between 0 and 2π.
var delta = this.options.speed / this.arc.r;
this.arc.t += this.particle.clockwise ? -delta : +delta;
this.arc.t = this.arc.t > 0 ? this.arc.t % _enums.RAD.t360 : _enums.RAD.t360 - this.arc.t;
this.particle.x = this.arc.x + this.arc.r * Math.cos(this.arc.t);
this.particle.y = this.arc.y - this.arc.r * Math.sin(this.arc.t);
// Overflow.
if (this.particle.x < 0) {
this.particle.x += this.bounds.width;
this.arc.x += this.bounds.width;
} else if (this.particle.x > this.bounds.width) {
this.particle.x -= this.bounds.width;
this.arc.x -= this.bounds.width;
}
if (this.particle.y < 0) {
this.particle.y += this.bounds.height; // TODO size of area
this.arc.y += this.bounds.height;
} else if (this.particle.y > this.bounds.height) {
this.particle.y -= this.bounds.height;
this.arc.y -= this.bounds.height;
}
};
function moveArc(arc, newRadius) {
var r0 = arc.r;
var r1 = newRadius;
@ -6458,149 +6663,51 @@ function calculateVisionGridPoints() {
}, []);
}
function Particle(container, bounds) {
var _this = this;
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
this.container = container;
this.bounds = bounds;
this.visionGridPoints = calculateVisionGridPoints();
this.node = document.createElement('div');
this.node.className = 'particle has-vision';
this.circle = document.createElement('div');
this.circle.className = 'particle-movement-circle';
this.container.appendChild(this.node);
this.container.appendChild(this.circle);
this.visionGridPoints.forEach(function (point) {
_this.container.appendChild(point.div);
});
this.arc = {
r: random.num(100, 200),
t: random.num(0, _enums.RAD.t360),
x: random.num(0, bounds.width),
y: random.num(0, bounds.height)
};
this.particle = {
clockwise: random.bool(),
speed: 4,
x: 0,
y: 0
};
this.interval = 0;
function repaintParticle(arc, node, particle) {
var rad = particle.clockwise ? _enums.RAD.t180 - arc.t : _enums.RAD.t360 - arc.t;
this.updateOptions(options);
this.nextFrame();
};
Particle.prototype.nextFrame = function () {
this.move();
this.repaintParticle();
this.repaintCircle();
this.repaintVisionGrid();
};
Particle.prototype.updateOptions = function (options) {
this.particleImage = 'seahorse';
this.randomlyChangeRadius = new Boolean(options.randomlyChangeRadius) || true;
this.randomlyChangeRotation = new Boolean(options.randomlyChangeRotation) || true;
this.showCircle = new Boolean(options.showCircle) || false;
this.showVision = new Boolean(options.showVision) || false;
};
Particle.prototype.repaintParticle = function () {
var rad = this.particle.clockwise ? _enums.RAD.t180 - this.arc.t : _enums.RAD.t360 - this.arc.t;
this.node.style.left = this.particle.x + 'px';
this.node.style.top = this.particle.y + 'px';
this.node.style.transform = 'rotate(' + rad + 'rad)';
};
Particle.prototype.repaintCircle = function () {
this.circle.style.width = 2 * this.arc.r + 'px';
this.circle.style.height = 2 * this.arc.r + 'px';
this.circle.style.left = this.arc.x - this.arc.r + 'px';
this.circle.style.top = this.arc.y - this.arc.r + 'px';
node.style.left = particle.x + 'px';
node.style.top = particle.y + 'px';
node.style.transform = 'rotate(' + rad + 'rad)';
}
this.circle.style.borderRadius = this.arc.r + 'px';
};
function repaintCircle(arc, node) {
node.style.width = 2 * arc.r + 'px';
node.style.height = 2 * arc.r + 'px';
node.style.left = arc.x - arc.r + 'px';
node.style.top = arc.y - arc.r + 'px';
Particle.prototype.repaintVisionGrid = function () {
var _this2 = this;
node.style.borderRadius = arc.r + 'px';
}
var r0 = Math.min(this.arc.t, this.arc.t - _enums.RAD.t180);
var r1 = Math.max(this.arc.t, this.arc.t + _enums.RAD.t180);
function repaintVisionGrid(arc, particle, points) {
var r0 = Math.min(arc.t, arc.t - _enums.RAD.t180);
var r1 = Math.max(arc.t, arc.t + _enums.RAD.t180);
var gridX = this.particle.x - this.particle.x % 5;
var gridY = this.particle.y - this.particle.y % 5;
var gridX = particle.x - particle.x % 5;
var gridY = particle.y - particle.y % 5;
this.visionGridPoints.forEach(function (_ref, i) {
points.forEach(function (_ref, i) {
var x = _ref.x,
y = _ref.y,
alpha = _ref.alpha,
div = _ref.div;
if (alpha >= 0 && alpha <= r0) {
div.style.display = _this2.particle.clockwise ? 'none' : 'block';
div.style.display = particle.clockwise ? 'none' : 'block';
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else if (alpha >= _this2.arc.t && alpha <= r1) {
div.style.display = _this2.particle.clockwise ? 'none' : 'block';
} else if (alpha >= arc.t && alpha <= r1) {
div.style.display = particle.clockwise ? 'none' : 'block';
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else {
div.style.display = _this2.particle.clockwise ? 'block' : 'none';
div.style.display = particle.clockwise ? 'block' : 'none';
// div.className = (clockwise ? 'anim3-dot' : 'anim3-dot removed');
}
div.style.left = x + gridX + 'px';
div.style.top = -y + gridY + 'px';
});
};
Particle.prototype.move = function () {
// Randomly change radius and rotation direction.
this.interval -= 1;
if (this.interval <= 0) {
this.interval = random.num(50, 100);
this.arc = moveArc(this.arc, random.num(100, 200));
if (random.bool()) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
}
}
// Ensure constant velocity and theta between 0 and 2π.
var delta = this.particle.speed / this.arc.r;
this.arc.t += this.particle.clockwise ? -delta : +delta;
this.arc.t = this.arc.t > 0 ? this.arc.t % _enums.RAD.t360 : _enums.RAD.t360 - this.arc.t;
this.particle.x = this.arc.x + this.arc.r * Math.cos(this.arc.t);
this.particle.y = this.arc.y - this.arc.r * Math.sin(this.arc.t);
// Overflow.
if (this.particle.x < 0) {
this.particle.x += this.bounds.width;
this.arc.x += this.bounds.width;
} else if (this.particle.x > this.bounds.width) {
this.particle.x -= this.bounds.width;
this.arc.x -= this.bounds.width;
}
if (this.particle.y < 0) {
this.particle.y += this.bounds.height; // TODO size of area
this.arc.y += this.bounds.height;
} else if (this.particle.y > this.bounds.height) {
this.particle.y -= this.bounds.height;
this.arc.y -= this.bounds.height;
}
};
}
exports.default = Particle;

138
js/controls.js vendored

@ -1,75 +1,108 @@
import Rx, { Observable } from 'rxjs';
import { CONTROLS } from './enums';
function Controls(container, { animating, count, randomizeRadius, randomizeRotation, speed }) {
this.nodes = {
animating: createAnimatingControl(animating),
count: createCountControl(count),
radius: createRadiusControl(randomizeRadius),
rotation: createRotationControl(randomizeRotation),
speed: createSpeedControl(speed),
}
container.appendChild(this.nodes.count);
container.appendChild(this.nodes.radius);
container.appendChild(this.nodes.rotation);
container.appendChild(this.nodes.speed);
container.appendChild(this.nodes.animating);
this.updateOptions({ key: CONTROLS.ANIMATING, value: animating });
this.updateOptions({ key: CONTROLS.COUNT, value: count });
this.updateOptions({ key: CONTROLS.RADIUS, value: randomizeRadius });
this.updateOptions({ key: CONTROLS.ROTATION, value: randomizeRotation });
this.updateOptions({ key: CONTROLS.SPEED, value: speed });
}
function Controls(node, options) {
this.node = node;
this.options = options;
Controls.prototype.updateOptions = function({ key, value}) {
if (key === CONTROLS.COUNT) {
this.nodes.count.querySelector('span').innerHTML =
(value == 1) ? '1 particle' : `${value} particles`;
}
this.countCtrl = createCountControl();
this.speedCtrl = createSpeedControl();
this.radiusCtrl = createRadiusControl();
this.rotationCtrl = createRotationControl();
this.animatingCtrl = createAnimatingControl();
if (key === CONTROLS.SPEED) {
this.nodes.speed.querySelector('span').innerHTML =
`Speed: ${value}`;
}
this.updateOptions(options);
}
if (key === CONTROLS.ANIMATING) {
this.nodes.animating.querySelector('span').innerHTML =
(value ? '&#9724; Stop' : '&#9654; Start');
}
Controls.prototype.updateOptions = function({ animating, count, speed }) {
this.animatingCtrl.innerHTML = (animating ? '&#9724; Stop' : '&#9654; Start');
}
Controls.prototype.mount = function(customNodes) {
this.node.appendChild(this.countCtrl);
this.node.appendChild(this.speedCtrl);
this.node.appendChild(this.radiusCtrl);
this.node.appendChild(this.rotationCtrl);
this.node.appendChild(this.animatingCtrl);
const animatingStream = Rx.Observable.fromEvent(this.nodes.animating, 'change')
.map(evt => ({ key: CONTROLS.ANIMATING, value: evt.target.checked }));
const countStream = Rx.Observable.fromEvent(this.nodes.count, 'input')
.map(evt => ({ key: CONTROLS.COUNT, value: evt.target.value * 1 }));
const radiusStream = Rx.Observable.fromEvent(this.nodes.radius, 'change')
.map(evt => ({ key: CONTROLS.RADIUS, value: evt.target.checked }));
const rotationStream = Rx.Observable.fromEvent(this.nodes.rotation, 'change')
.map(evt => ({ key: CONTROLS.ROTATION, value: evt.target.checked }));
const speedStream = Rx.Observable.fromEvent(this.nodes.speed, 'input')
.map(evt => ({ key: CONTROLS.SPEED, value: evt.target.value * 1 }));
animatingStream.subscribe(this.updateOptions.bind(this));
countStream.subscribe(this.updateOptions.bind(this));
radiusStream.subscribe(this.updateOptions.bind(this));
rotationStream.subscribe(this.updateOptions.bind(this));
speedStream.subscribe(this.updateOptions.bind(this));
return Rx.Observable.merge(
Rx.Observable.fromEvent(this.countCtrl, 'change')
.map(evt => ({ key: 'count', value: evt.target.value * 1 })),
Rx.Observable.fromEvent(this.speedCtrl, 'change')
.map(evt => ({ key: 'speed', value: evt.target.value * 1 })),
Rx.Observable.fromEvent(this.radiusCtrl, 'change')
.map(evt => ({ key: 'radius', value: evt.target.checked })),
Rx.Observable.fromEvent(this.rotationCtrl, 'change')
.map(evt => ({ key: 'rotation', value: evt.target.checked })),
Rx.Observable.fromEvent(this.animatingCtrl, 'click')
.map(evt => ({ key: 'animating', value: null })),
animatingStream,
countStream,
radiusStream,
rotationStream,
speedStream,
);
}
const createCountControl = function() {
const createAnimatingControl = function(value) {
const label = document.createElement('label');
label.className = 'controls-label';
label.className = 'controls-animating';
const text = document.createElement('span');
text.innerHTML = 'Number of particles';
text.className = 'controls-label-text';
text.innerHTML = '...';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.className = 'controls-slider';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
label.appendChild(slider);
return label;
}
const createSpeedControl = function() {
const createCountControl = function(value) {
const label = document.createElement('label');
label.className = 'controls-label';
const text = document.createElement('span');
text.innerHTML = 'Animation speed';
text.innerHTML = '...';
text.className = 'controls-label-text';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.max = 5;
slider.value = value;
slider.className = 'controls-slider';
label.appendChild(text);
@ -78,7 +111,7 @@ const createSpeedControl = function() {
return label;
}
const createRadiusControl = function() {
const createRadiusControl = function(value) {
const label = document.createElement('label');
label.className = 'controls-label';
@ -89,6 +122,7 @@ const createRadiusControl = function() {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-label-checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
@ -96,7 +130,7 @@ const createRadiusControl = function() {
return label;
}
const createRotationControl = function() {
const createRotationControl = function(value) {
const label = document.createElement('label');
label.className = 'controls-label';
@ -107,6 +141,7 @@ const createRotationControl = function() {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-label-checkbox';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
@ -114,11 +149,24 @@ const createRotationControl = function() {
return label;
}
const createAnimatingControl = function() {
const button = document.createElement('button');
button.className = 'controls-button controls-bottom';
const createSpeedControl = function(value) {
const label = document.createElement('label');
label.className = 'controls-label';
const text = document.createElement('span');
text.className = 'controls-label-text';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 10;
slider.value = value;
slider.className = 'controls-slider';
label.appendChild(text);
label.appendChild(slider);
return button;
return label;
}
export default Controls;

@ -6,4 +6,16 @@ const RAD = {
t360: Math.PI * 2
};
export { RAD };
const CONTROLS = {
ANIMATING: 'animating',
COUNT: 'count',
RADIUS: 'radius',
ROTATION: 'rotation',
SPEED: 'speed'
};
const IMAGES = {
SEAHORSE: 'seahorse'
};
export { CONTROLS, IMAGES, RAD };

@ -8,6 +8,114 @@ const random = {
num: (min, max) => min + Math.round(Math.random() * max)
}
/* ===== Constructor ===== */
function Particle(container, bounds, options) {
this.container = container;
this.bounds = bounds;
this.visionGridPoints = calculateVisionGridPoints();
this.node = document.createElement('div');
this.node.className = 'particle has-vision';
this.circle = document.createElement('div');
this.circle.className = 'particle-movement-circle';
this.container.appendChild(this.node);
this.container.appendChild(this.circle);
this.visionGridPoints.forEach(point => {
this.container.appendChild(point.div);
});
this.options = Object.assign({
randomizeRadius: false,
randomizeRotation: false,
showMovementCircle: false,
showVIsionGrid: false,
speed: 4
}, options);
this.arc = {
r: random.num(100, 200),
t: random.num(0, RAD.t360),
x: random.num(0, bounds.width),
y: random.num(0, bounds.height)
}
this.particle = {
clockwise: random.bool(),
x: 0,
y: 0
}
this.interval = 0;
this.updateOptions(this.options);
this.nextFrame();
};
Particle.prototype.nextFrame = function() {
this.move();
repaintParticle(this.arc, this.node, this.particle);
if (this.options.showMovementCircle) {
repaintCircle(this.arc, this.circle);
}
if (this.options.showVisionGrid) {
repaintVisionGrid(this.arc, this.particle, this.visionGridPoints);
}
}
Particle.prototype.updateOptions = function(options) {
Object.assign(this.options, options);
// this.particleImage = options.image || IMAGES.SEAHORSE;
}
Particle.prototype.move = function() {
// Randomly change radius and rotation direction.
this.interval -= 1;
if (this.interval <= 0) {
this.interval = random.num(50, 100);
if (this.options.randomizeRadius === true) {
this.arc = moveArc(this.arc, random.num(100, 200));
if (random.bool() && this.options.randomizeRotation === true) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
}
}
}
// Ensure constant velocity and theta between 0 and 2π.
const delta = this.options.speed / this.arc.r;
this.arc.t += (this.particle.clockwise ? -delta : +delta);
this.arc.t = (this.arc.t > 0 ? this.arc.t % RAD.t360 : RAD.t360 - this.arc.t);
this.particle.x = this.arc.x + this.arc.r * Math.cos(this.arc.t);
this.particle.y = this.arc.y - this.arc.r * Math.sin(this.arc.t);
// Overflow.
if (this.particle.x < 0) {
this.particle.x += this.bounds.width;
this.arc.x += this.bounds.width
} else if (this.particle.x > this.bounds.width) {
this.particle.x -= this.bounds.width;
this.arc.x -= this.bounds.width
}
if (this.particle.y < 0) {
this.particle.y += this.bounds.height; // TODO size of area
this.arc.y += this.bounds.height
} else if (this.particle.y > this.bounds.height) {
this.particle.y -= this.bounds.height;
this.arc.y -= this.bounds.height
}
}
function moveArc(arc, newRadius) {
const r0 = arc.r;
const r1 = newRadius;
@ -74,94 +182,41 @@ function calculateVisionGridPoints() {
}, []);
}
function Particle(container, bounds, options = {}) {
this.container = container;
this.bounds = bounds;
this.visionGridPoints = calculateVisionGridPoints();
this.node = document.createElement('div');
this.node.className = 'particle has-vision';
this.circle = document.createElement('div');
this.circle.className = 'particle-movement-circle';
this.container.appendChild(this.node);
this.container.appendChild(this.circle);
this.visionGridPoints.forEach(point => {
this.container.appendChild(point.div);
});
this.arc = {
r: random.num(100, 200),
t: random.num(0, RAD.t360),
x: random.num(0, bounds.width),
y: random.num(0, bounds.height)
}
this.particle = {
clockwise: random.bool(),
speed: 4,
x: 0,
y: 0
}
this.interval = 0;
this.updateOptions(options);
this.nextFrame();
};
Particle.prototype.nextFrame = function() {
this.move();
this.repaintParticle();
this.repaintCircle();
this.repaintVisionGrid();
}
Particle.prototype.updateOptions = function(options) {
this.particleImage = 'seahorse';
this.randomlyChangeRadius = (new Boolean(options.randomlyChangeRadius)) || true;
this.randomlyChangeRotation = (new Boolean(options.randomlyChangeRotation)) || true;
this.showCircle = (new Boolean(options.showCircle)) || false;
this.showVision = (new Boolean(options.showVision)) || false;
}
Particle.prototype.repaintParticle = function() {
const rad = this.particle.clockwise
? RAD.t180 - this.arc.t
: RAD.t360 - this.arc.t;
function repaintParticle(arc, node, particle) {
const rad = particle.clockwise
? RAD.t180 - arc.t
: RAD.t360 - arc.t;
this.node.style.left = `${this.particle.x}px`;
this.node.style.top = `${this.particle.y}px`;
this.node.style.transform = `rotate(${rad}rad)`;
node.style.left = `${particle.x}px`;
node.style.top = `${particle.y}px`;
node.style.transform = `rotate(${rad}rad)`;
}
Particle.prototype.repaintCircle = function() {
this.circle.style.width = `${2 * this.arc.r}px`;
this.circle.style.height = `${2 * this.arc.r}px`;
this.circle.style.left = `${this.arc.x - this.arc.r}px`;
this.circle.style.top = `${this.arc.y - this.arc.r}px`;
function repaintCircle(arc, node) {
node.style.width = `${2 * arc.r}px`;
node.style.height = `${2 * arc.r}px`;
node.style.left = `${arc.x - arc.r}px`;
node.style.top = `${arc.y - arc.r}px`;
this.circle.style.borderRadius = `${this.arc.r}px`;
node.style.borderRadius = `${arc.r}px`;
}
Particle.prototype.repaintVisionGrid = function() {
const r0 = Math.min(this.arc.t, this.arc.t - RAD.t180);
const r1 = Math.max(this.arc.t, this.arc.t + RAD.t180);
function repaintVisionGrid(arc, particle, points) {
const r0 = Math.min(arc.t, arc.t - RAD.t180);
const r1 = Math.max(arc.t, arc.t + RAD.t180);
const gridX = this.particle.x - this.particle.x % 5;
const gridY = this.particle.y - this.particle.y % 5;
const gridX = particle.x - particle.x % 5;
const gridY = particle.y - particle.y % 5;
this.visionGridPoints.forEach(({ x, y, alpha, div }, i) => {
points.forEach(({ x, y, alpha, div }, i) => {
if (alpha >= 0 && alpha <= r0) {
div.style.display = (this.particle.clockwise ? 'none' : 'block');
div.style.display = (particle.clockwise ? 'none' : 'block');
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else if (alpha >= this.arc.t && alpha <= r1) {
div.style.display = (this.particle.clockwise ? 'none' : 'block');
} else if (alpha >= arc.t && alpha <= r1) {
div.style.display = (particle.clockwise ? 'none' : 'block');
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else {
div.style.display = (this.particle.clockwise ? 'block' : 'none');
div.style.display = (particle.clockwise ? 'block' : 'none');
// div.className = (clockwise ? 'anim3-dot' : 'anim3-dot removed');
}
@ -170,44 +225,5 @@ Particle.prototype.repaintVisionGrid = function() {
});
}
Particle.prototype.move = function() {
// Randomly change radius and rotation direction.
this.interval -= 1;
if (this.interval <= 0) {
this.interval = random.num(50, 100);
this.arc = moveArc(this.arc, random.num(100, 200));
if (random.bool()) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
}
}
// Ensure constant velocity and theta between 0 and 2π.
const delta = this.particle.speed / this.arc.r;
this.arc.t += (this.particle.clockwise ? -delta : +delta);
this.arc.t = (this.arc.t > 0 ? this.arc.t % RAD.t360 : RAD.t360 - this.arc.t);
this.particle.x = this.arc.x + this.arc.r * Math.cos(this.arc.t);
this.particle.y = this.arc.y - this.arc.r * Math.sin(this.arc.t);
// Overflow.
if (this.particle.x < 0) {
this.particle.x += this.bounds.width;
this.arc.x += this.bounds.width
} else if (this.particle.x > this.bounds.width) {
this.particle.x -= this.bounds.width;
this.arc.x -= this.bounds.width
}
if (this.particle.y < 0) {
this.particle.y += this.bounds.height; // TODO size of area
this.arc.y += this.bounds.height
} else if (this.particle.y > this.bounds.height) {
this.particle.y -= this.bounds.height;
this.arc.y -= this.bounds.height
}
}
export default Particle;

Loading…
Cancel
Save