import Super from './super'; import * as u from './utils'; /////////////////////// /// THE NIPPLE /// /////////////////////// function Nipple (collection, options) { this.identifier = options.identifier; this.position = options.position; this.frontPosition = options.frontPosition; this.collection = collection; // Defaults this.defaults = { size: 100, threshold: 0.1, color: 'white', fadeTime: 250, dataOnly: false, restJoystick: true, restOpacity: 0.5, mode: 'dynamic', zone: document.body, lockX: false, lockY: false, shape: 'circle' }; this.config(options); // Overwrites if (this.options.mode === 'dynamic') { this.options.restOpacity = 0; } this.id = Nipple.id; Nipple.id += 1; this.buildEl() .stylize(); // Nipple's API. this.instance = { el: this.ui.el, on: this.on.bind(this), off: this.off.bind(this), show: this.show.bind(this), hide: this.hide.bind(this), add: this.addToDom.bind(this), remove: this.removeFromDom.bind(this), destroy: this.destroy.bind(this), setPosition:this.setPosition.bind(this), resetDirection: this.resetDirection.bind(this), computeDirection: this.computeDirection.bind(this), trigger: this.trigger.bind(this), position: this.position, frontPosition: this.frontPosition, ui: this.ui, identifier: this.identifier, id: this.id, options: this.options }; return this.instance; } Nipple.prototype = new Super(); Nipple.constructor = Nipple; Nipple.id = 0; // Build the dom element of the Nipple instance. Nipple.prototype.buildEl = function (options) { this.ui = {}; if (this.options.dataOnly) { return this; } this.ui.el = document.createElement('div'); this.ui.back = document.createElement('div'); this.ui.front = document.createElement('div'); this.ui.el.className = 'nipple collection_' + this.collection.id; this.ui.back.className = 'back'; this.ui.front.className = 'front'; this.ui.el.setAttribute('id', 'nipple_' + this.collection.id + '_' + this.id); this.ui.el.appendChild(this.ui.back); this.ui.el.appendChild(this.ui.front); return this; }; // Apply CSS to the Nipple instance. Nipple.prototype.stylize = function () { if (this.options.dataOnly) { return this; } var animTime = this.options.fadeTime + 'ms'; var borderStyle = u.getVendorStyle('borderRadius', '50%'); var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime); var styles = {}; styles.el = { position: 'absolute', opacity: this.options.restOpacity, display: 'block', 'zIndex': 999 }; styles.back = { position: 'absolute', display: 'block', width: this.options.size + 'px', height: this.options.size + 'px', marginLeft: -this.options.size / 2 + 'px', marginTop: -this.options.size / 2 + 'px', background: this.options.color, 'opacity': '.5' }; styles.front = { width: this.options.size / 2 + 'px', height: this.options.size / 2 + 'px', position: 'absolute', display: 'block', marginLeft: -this.options.size / 4 + 'px', marginTop: -this.options.size / 4 + 'px', background: this.options.color, 'opacity': '.5' }; u.extend(styles.el, transitStyle); if(this.options.shape === 'circle'){ u.extend(styles.back, borderStyle); } u.extend(styles.front, borderStyle); this.applyStyles(styles); return this; }; Nipple.prototype.applyStyles = function (styles) { // Apply styles for (var i in this.ui) { if (this.ui.hasOwnProperty(i)) { for (var j in styles[i]) { this.ui[i].style[j] = styles[i][j]; } } } return this; }; // Inject the Nipple instance into DOM. Nipple.prototype.addToDom = function () { // We're not adding it if we're dataOnly or already in dom. if (this.options.dataOnly || document.body.contains(this.ui.el)) { return this; } this.options.zone.appendChild(this.ui.el); return this; }; // Remove the Nipple instance from DOM. Nipple.prototype.removeFromDom = function () { if (this.options.dataOnly || !document.body.contains(this.ui.el)) { return this; } this.options.zone.removeChild(this.ui.el); return this; }; // Entirely destroy this nipple Nipple.prototype.destroy = function () { clearTimeout(this.removeTimeout); clearTimeout(this.showTimeout); clearTimeout(this.restTimeout); this.trigger('destroyed', this.instance); this.removeFromDom(); this.off(); }; // Fade in the Nipple instance. Nipple.prototype.show = function (cb) { var self = this; if (self.options.dataOnly) { return self; } clearTimeout(self.removeTimeout); clearTimeout(self.showTimeout); clearTimeout(self.restTimeout); self.addToDom(); self.restCallback(); setTimeout(function () { self.ui.el.style.opacity = 1; }, 0); self.showTimeout = setTimeout(function () { self.trigger('shown', self.instance); if (typeof cb === 'function') { cb.call(this); } }, self.options.fadeTime); return self; }; // Fade out the Nipple instance. Nipple.prototype.hide = function (cb) { var self = this; if (self.options.dataOnly) { return self; } self.ui.el.style.opacity = self.options.restOpacity; clearTimeout(self.removeTimeout); clearTimeout(self.showTimeout); clearTimeout(self.restTimeout); self.removeTimeout = setTimeout( function () { var display = self.options.mode === 'dynamic' ? 'none' : 'block'; self.ui.el.style.display = display; if (typeof cb === 'function') { cb.call(self); } self.trigger('hidden', self.instance); }, self.options.fadeTime ); if (self.options.restJoystick) { const rest = self.options.restJoystick; const newPosition = {}; newPosition.x = rest === true || rest.x !== false ? 0 : self.instance.frontPosition.x; newPosition.y = rest === true || rest.y !== false ? 0 : self.instance.frontPosition.y; self.setPosition(cb, newPosition); } return self; }; // Set the nipple to the specified position Nipple.prototype.setPosition = function (cb, position) { var self = this; self.frontPosition = { x: position.x, y: position.y }; var animTime = self.options.fadeTime + 'ms'; var transitStyle = {}; transitStyle.front = u.getTransitionStyle('transition', ['top', 'left'], animTime); var styles = {front: {}}; styles.front = { left: self.frontPosition.x + 'px', top: self.frontPosition.y + 'px' }; self.applyStyles(transitStyle); self.applyStyles(styles); self.restTimeout = setTimeout( function () { if (typeof cb === 'function') { cb.call(self); } self.restCallback(); }, self.options.fadeTime ); }; Nipple.prototype.restCallback = function () { var self = this; var transitStyle = {}; transitStyle.front = u.getTransitionStyle('transition', 'none', ''); self.applyStyles(transitStyle); self.trigger('rested', self.instance); }; Nipple.prototype.resetDirection = function () { // Fully rebuild the object to let the iteration possible. this.direction = { x: false, y: false, angle: false }; }; Nipple.prototype.computeDirection = function (obj) { var rAngle = obj.angle.radian; var angle45 = Math.PI / 4; var angle90 = Math.PI / 2; var direction, directionX, directionY; // Angular direction // \ UP / // \ / // LEFT RIGHT // / \ // /DOWN \ // if ( rAngle > angle45 && rAngle < (angle45 * 3) && !obj.lockX ) { direction = 'up'; } else if ( rAngle > -angle45 && rAngle <= angle45 && !obj.lockY ) { direction = 'left'; } else if ( rAngle > (-angle45 * 3) && rAngle <= -angle45 && !obj.lockX ) { direction = 'down'; } else if (!obj.lockY) { direction = 'right'; } // Plain direction // UP | // _______ | RIGHT // LEFT | // DOWN | if (!obj.lockY) { if (rAngle > -angle90 && rAngle < angle90) { directionX = 'left'; } else { directionX = 'right'; } } if (!obj.lockX) { if (rAngle > 0) { directionY = 'up'; } else { directionY = 'down'; } } if (obj.force > this.options.threshold) { var oldDirection = {}; var i; for (i in this.direction) { if (this.direction.hasOwnProperty(i)) { oldDirection[i] = this.direction[i]; } } var same = {}; this.direction = { x: directionX, y: directionY, angle: direction }; obj.direction = this.direction; for (i in oldDirection) { if (oldDirection[i] === this.direction[i]) { same[i] = true; } } // If all 3 directions are the same, we don't trigger anything. if (same.x && same.y && same.angle) { return obj; } if (!same.x || !same.y) { this.trigger('plain', obj); } if (!same.x) { this.trigger('plain:' + directionX, obj); } if (!same.y) { this.trigger('plain:' + directionY, obj); } if (!same.angle) { this.trigger('dir dir:' + direction, obj); } } else { this.resetDirection(); } return obj; }; export default Nipple;