From 466f0f400fb7c02e31539e20c7dacf676ca395db Mon Sep 17 00:00:00 2001 From: Ben Burlingham Date: Sun, 20 Aug 2017 17:37:25 -0700 Subject: [PATCH] Sidebar UI all hooked up. --- css/controls.scss | 9 +- css/style.css | 13 +- js/animation1.js | 81 +++++--- js/bundle.js | 515 ++++++++++++++++++++++++++++------------------ js/controls.js | 138 +++++++++---- js/enums.js | 14 +- js/particle.js | 246 +++++++++++----------- 7 files changed, 614 insertions(+), 402 deletions(-) diff --git a/css/controls.scss b/css/controls.scss index 7655e27..fd4edce 100644 --- a/css/controls.scss +++ b/css/controls.scss @@ -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; diff --git a/css/style.css b/css/style.css index 31f192c..5dd3b91 100644 --- a/css/style.css +++ b/css/style.css @@ -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 { diff --git a/js/animation1.js b/js/animation1.js index d0cacfa..fc3f656 100644 --- a/js/animation1.js +++ b/js/animation1.js @@ -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; diff --git a/js/bundle.js b/js/bundle.js index d5ebe52..fb5629e 100644 --- a/js/bundle.js +++ b/js/bundle.js @@ -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 ? '◼ Stop' : '▶ 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 ? '◼ Stop' : '▶ 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; diff --git a/js/controls.js b/js/controls.js index 724b919..5cebe4a 100644 --- a/js/controls.js +++ b/js/controls.js @@ -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 ? '◼ Stop' : '▶ Start'); + } -Controls.prototype.updateOptions = function({ animating, count, speed }) { - this.animatingCtrl.innerHTML = (animating ? '◼ Stop' : '▶ 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; diff --git a/js/enums.js b/js/enums.js index 2cb4762..7b2187b 100644 --- a/js/enums.js +++ b/js/enums.js @@ -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 }; diff --git a/js/particle.js b/js/particle.js index 6ad50df..fb6fc3d 100644 --- a/js/particle.js +++ b/js/particle.js @@ -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;