418 lines
10 KiB
JavaScript
418 lines
10 KiB
JavaScript
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',
|
|
left: 0,
|
|
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',
|
|
left: 0,
|
|
marginLeft: -this.options.size / 4 + 'px',
|
|
marginTop: -this.options.size / 4 + 'px',
|
|
background: this.options.color,
|
|
'opacity': '.5',
|
|
transform: 'translate(0px, 0px)'
|
|
};
|
|
|
|
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',
|
|
['transform'], animTime);
|
|
|
|
var styles = {front: {}};
|
|
styles.front = {
|
|
transform: 'translate(' + self.frontPosition.x + 'px,' + 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;
|