Files
2025-11-30 08:35:03 +02:00

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;