12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412 |
- (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.nipplejs = f()}})(function(){var define,module,exports;
- 'use strict';
- // Constants
- var isTouch = !!('ontouchstart' in window);
- var isPointer = window.PointerEvent ? true : false;
- var isMSPointer = window.MSPointerEvent ? true : false;
- var events = {
- touch: {
- start: 'touchstart',
- move: 'touchmove',
- end: 'touchend'
- },
- mouse: {
- start: 'mousedown',
- move: 'mousemove',
- end: 'mouseup'
- },
- pointer: {
- start: 'pointerdown',
- move: 'pointermove',
- end: 'pointerup'
- },
- MSPointer: {
- start: 'MSPointerDown',
- move: 'MSPointerMove',
- end: 'MSPointerUp'
- }
- };
- var toBind;
- var secondBind = {};
- if (isPointer) {
- toBind = events.pointer;
- } else if (isMSPointer) {
- toBind = events.MSPointer;
- } else if (isTouch) {
- toBind = events.touch;
- secondBind = events.mouse;
- } else {
- toBind = events.mouse;
- }
- ///////////////////////
- /// UTILS ///
- ///////////////////////
- var u = {};
- u.distance = function (p1, p2) {
- var dx = p2.x - p1.x;
- var dy = p2.y - p1.y;
- return Math.sqrt((dx * dx) + (dy * dy));
- };
- u.angle = function(p1, p2) {
- var dx = p2.x - p1.x;
- var dy = p2.y - p1.y;
- return u.degrees(Math.atan2(dy, dx));
- };
- u.findCoord = function(p, d, a) {
- var b = {x: 0, y: 0};
- a = u.radians(a);
- b.x = p.x - d * Math.cos(a);
- b.y = p.y - d * Math.sin(a);
- return b;
- };
- u.radians = function(a) {
- return a * (Math.PI / 180);
- };
- u.degrees = function(a) {
- return a * (180 / Math.PI);
- };
- u.bindEvt = function (el, type, handler) {
- if (el.addEventListener) {
- el.addEventListener(type, handler, false);
- } else if (el.attachEvent) {
- el.attachEvent(type, handler);
- }
- };
- u.unbindEvt = function (el, type, handler) {
- if (el.removeEventListener) {
- el.removeEventListener(type, handler);
- } else if (el.detachEvent) {
- el.detachEvent(type, handler);
- }
- };
- u.trigger = function (el, type, data) {
- var evt = new CustomEvent(type, data);
- el.dispatchEvent(evt);
- };
- u.prepareEvent = function (evt) {
- evt.preventDefault();
- return evt.type.match(/^touch/) ? evt.changedTouches : evt;
- };
- u.getScroll = function () {
- var x = (window.pageXOffset !== undefined) ?
- window.pageXOffset :
- (document.documentElement || document.body.parentNode || document.body)
- .scrollLeft;
- var y = (window.pageYOffset !== undefined) ?
- window.pageYOffset :
- (document.documentElement || document.body.parentNode || document.body)
- .scrollTop;
- return {
- x: x,
- y: y
- };
- };
- u.applyPosition = function (el, pos) {
- if (pos.x && pos.y) {
- el.style.left = pos.x + 'px';
- el.style.top = pos.y + 'px';
- } else if (pos.top || pos.right || pos.bottom || pos.left) {
- el.style.top = pos.top;
- el.style.right = pos.right;
- el.style.bottom = pos.bottom;
- el.style.left = pos.left;
- }
- };
- u.getTransitionStyle = function (property, values, time) {
- var obj = u.configStylePropertyObject(property);
- for (var i in obj) {
- if (obj.hasOwnProperty(i)) {
- if (typeof values === 'string') {
- obj[i] = values + ' ' + time;
- } else {
- var st = '';
- for (var j = 0, max = values.length; j < max; j += 1) {
- st += values[j] + ' ' + time + ', ';
- }
- obj[i] = st.slice(0, -2);
- }
- }
- }
- return obj;
- };
- u.getVendorStyle = function (property, value) {
- var obj = u.configStylePropertyObject(property);
- for (var i in obj) {
- if (obj.hasOwnProperty(i)) {
- obj[i] = value;
- }
- }
- return obj;
- };
- u.configStylePropertyObject = function (prop) {
- var obj = {};
- obj[prop] = '';
- var vendors = ['webkit', 'Moz', 'o'];
- vendors.forEach(function (vendor) {
- obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';
- });
- return obj;
- };
- u.extend = function (objA, objB) {
- for (var i in objB) {
- if (objB.hasOwnProperty(i)) {
- objA[i] = objB[i];
- }
- }
- return objA;
- };
- // Overwrite only what's already present
- u.safeExtend = function (objA, objB) {
- var obj = {};
- for (var i in objA) {
- if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {
- obj[i] = objB[i];
- } else if (objA.hasOwnProperty(i)) {
- obj[i] = objA[i];
- }
- }
- return obj;
- };
- // Map for array or unique item.
- u.map = function (ar, fn) {
- if (ar.length) {
- for (var i = 0, max = ar.length; i < max; i += 1) {
- fn(ar[i]);
- }
- } else {
- fn(ar);
- }
- };
- ///////////////////////
- /// SUPER CLASS ///
- ///////////////////////
- function Super () {};
- // Basic event system.
- Super.prototype.on = function (arg, cb) {
- var self = this;
- var types = arg.split(/[ ,]+/g);
- var type;
- self._handlers_ = self._handlers_ || {};
- for (var i = 0; i < types.length; i += 1) {
- type = types[i];
- self._handlers_[type] = self._handlers_[type] || [];
- self._handlers_[type].push(cb);
- }
- return self;
- };
- Super.prototype.off = function (type, cb) {
- var self = this;
- self._handlers_ = self._handlers_ || {};
- if (type === undefined) {
- self._handlers_ = {};
- } else if (cb === undefined) {
- self._handlers_[type] = null;
- } else if (self._handlers_[type] &&
- self._handlers_[type].indexOf(cb) >= 0) {
- self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);
- }
- return self;
- };
- Super.prototype.trigger = function (arg, data) {
- var self = this;
- var types = arg.split(/[ ,]+/g);
- var type;
- self._handlers_ = self._handlers_ || {};
- for (var i = 0; i < types.length; i += 1) {
- type = types[i];
- if (self._handlers_[type] && self._handlers_[type].length) {
- self._handlers_[type].forEach(function (handler) {
- handler.call(self, {
- type: type,
- target: self
- }, data);
- });
- }
- }
- };
- // Configuration
- Super.prototype.config = function (options) {
- var self = this;
- self.options = self.defaults || {};
- if (options) {
- self.options = u.safeExtend(self.options, options);
- }
- };
- // Bind internal events.
- Super.prototype.bindEvt = function (el, type) {
- var self = this;
- self._domHandlers_ = self._domHandlers_ || {};
- self._domHandlers_[type] = function () {
- if (typeof self['on' + type] === 'function') {
- self['on' + type].apply(self, arguments);
- } else {
- console.warn('[WARNING] : Missing "on' + type + '" handler.');
- }
- };
- u.bindEvt(el, toBind[type], self._domHandlers_[type]);
- if (secondBind[type]) {
- // Support for both touch and mouse at the same time.
- u.bindEvt(el, secondBind[type], self._domHandlers_[type]);
- }
- return self;
- };
- // Unbind dom events.
- Super.prototype.unbindEvt = function (el, type) {
- var self = this;
- self._domHandlers_ = self._domHandlers_ || {};
- u.unbindEvt(el, toBind[type], self._domHandlers_[type]);
- if (secondBind[type]) {
- // Support for both touch and mouse at the same time.
- u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);
- }
- delete self._domHandlers_[type];
- return this;
- };
- ///////////////////////
- /// 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,
- restOpacity: 0.5,
- mode: 'dynamic',
- zone: document.body
- };
- 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),
- 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);
- 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
- );
- self.restPosition();
- return self;
- };
- Nipple.prototype.restPosition = function (cb) {
- var self = this;
- self.frontPosition = {
- x: 0,
- y: 0
- };
- 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)) {
- direction = 'up';
- } else if (rAngle > -angle45 && rAngle <= angle45) {
- direction = 'left';
- } else if (rAngle > (-angle45 * 3) && rAngle <= -angle45) {
- direction = 'down';
- } else {
- direction = 'right';
- }
- // Plain direction
- // UP |
- // _______ | RIGHT
- // LEFT |
- // DOWN |
- if (rAngle > -angle90 && rAngle < angle90) {
- directionX = 'left';
- } else {
- directionX = 'right';
- }
- if (rAngle > 0) {
- directionY = 'up';
- } else {
- directionY = 'down';
- }
- if (obj.force > this.options.threshold) {
- var oldDirection = {};
- for (var 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 (var 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);
- }
- }
- return obj;
- };
- /* global Nipple, Super */
- ///////////////////////////
- /// THE COLLECTION ///
- ///////////////////////////
- function Collection (manager, options) {
- var self = this;
- self.nipples = [];
- self.idles = [];
- self.actives = [];
- self.ids = [];
- self.pressureIntervals = {};
- self.manager = manager;
- self.id = Collection.id;
- Collection.id += 1;
- // Defaults
- self.defaults = {
- zone: document.body,
- multitouch: false,
- maxNumberOfNipples: 10,
- mode: 'dynamic',
- position: {top: 0, left: 0},
- catchDistance: 200,
- size: 100,
- threshold: 0.1,
- color: 'white',
- fadeTime: 250,
- dataOnly: false,
- restOpacity: 0.5
- };
- self.config(options);
- // Overwrites
- if (self.options.mode === 'static' || self.options.mode === 'semi') {
- self.options.multitouch = false;
- }
- if (!self.options.multitouch) {
- self.options.maxNumberOfNipples = 1;
- }
- self.updateBox();
- self.prepareNipples();
- self.bindings();
- self.begin();
- return self.nipples;
- }
- Collection.prototype = new Super();
- Collection.constructor = Collection;
- Collection.id = 0;
- Collection.prototype.prepareNipples = function () {
- var self = this;
- var nips = self.nipples;
- // Public API Preparation.
- nips.on = self.on.bind(self);
- nips.off = self.off.bind(self);
- nips.options = self.options;
- nips.destroy = self.destroy.bind(self);
- nips.ids = self.ids;
- nips.id = self.id;
- nips.processOnMove = self.processOnMove.bind(self);
- nips.processOnEnd = self.processOnEnd.bind(self);
- nips.get = function (id) {
- if (id === undefined) {
- return nips[0];
- }
- for (var i = 0, max = nips.length; i < max; i += 1) {
- if (nips[i].identifier === id) {
- return nips[i];
- }
- }
- return false;
- };
- };
- Collection.prototype.bindings = function () {
- var self = this;
- // Touch start event.
- self.bindEvt(self.options.zone, 'start');
- // Avoid native touch actions (scroll, zoom etc...) on the zone.
- self.options.zone.style.touchAction = 'none';
- self.options.zone.style.msTouchAction = 'none';
- };
- Collection.prototype.begin = function () {
- var self = this;
- var opts = self.options;
- // We place our static nipple
- // if needed.
- if (opts.mode === 'static') {
- var nipple = self.createNipple(
- opts.position,
- self.manager.getIdentifier()
- );
- // Add it to the dom.
- nipple.add();
- // Store it in idles.
- self.idles.push(nipple);
- }
- };
- // Nipple Factory
- Collection.prototype.createNipple = function (position, identifier) {
- var self = this;
- var scroll = u.getScroll();
- var toPutOn = {};
- var opts = self.options;
- if (position.x && position.y) {
- toPutOn = {
- x: position.x -
- (scroll.x + self.box.left),
- y: position.y -
- (scroll.y + self.box.top)
- };
- } else if (
- position.top ||
- position.right ||
- position.bottom ||
- position.left
- ) {
- // We need to compute the position X / Y of the joystick.
- var dumb = document.createElement('DIV');
- dumb.style.display = 'hidden';
- dumb.style.top = position.top;
- dumb.style.right = position.right;
- dumb.style.bottom = position.bottom;
- dumb.style.left = position.left;
- dumb.style.position = 'absolute';
- opts.zone.appendChild(dumb);
- var dumbBox = dumb.getBoundingClientRect();
- opts.zone.removeChild(dumb);
- toPutOn = position;
- position = {
- x: dumbBox.left + scroll.x,
- y: dumbBox.top + scroll.y
- };
- }
- var nipple = new Nipple(self, {
- color: opts.color,
- size: opts.size,
- threshold: opts.threshold,
- fadeTime: opts.fadeTime,
- dataOnly: opts.dataOnly,
- restOpacity: opts.restOpacity,
- mode: opts.mode,
- identifier: identifier,
- position: position,
- zone: opts.zone,
- frontPosition: {
- x: 0,
- y: 0
- }
- });
- if (!opts.dataOnly) {
- u.applyPosition(nipple.ui.el, toPutOn);
- u.applyPosition(nipple.ui.front, nipple.frontPosition);
- }
- self.nipples.push(nipple);
- self.trigger('added ' + nipple.identifier + ':added', nipple);
- self.manager.trigger('added ' + nipple.identifier + ':added', nipple);
- self.bindNipple(nipple);
- return nipple;
- };
- Collection.prototype.updateBox = function () {
- var self = this;
- self.box = self.options.zone.getBoundingClientRect();
- };
- Collection.prototype.bindNipple = function (nipple) {
- var self = this;
- var type;
- // Bubble up identified events.
- var handler = function (evt, data) {
- // Identify the event type with the nipple's id.
- type = evt.type + ' ' + data.id + ':' + evt.type;
- self.trigger(type, data);
- };
- // When it gets destroyed.
- nipple.on('destroyed', self.onDestroyed.bind(self));
- // Other events that will get bubbled up.
- nipple.on('shown hidden rested dir plain', handler);
- nipple.on('dir:up dir:right dir:down dir:left', handler);
- nipple.on('plain:up plain:right plain:down plain:left', handler);
- };
- Collection.prototype.pressureFn = function (touch, nipple, identifier) {
- var self = this;
- var previousPressure = 0;
- clearInterval(self.pressureIntervals[identifier]);
- // Create an interval that will read the pressure every 100ms
- self.pressureIntervals[identifier] = setInterval(function () {
- var pressure = touch.force || touch.pressure ||
- touch.webkitForce || 0;
- if (pressure !== previousPressure) {
- nipple.trigger('pressure', pressure);
- self.trigger('pressure ' +
- nipple.identifier + ':pressure', pressure);
- previousPressure = pressure;
- }
- }.bind(self), 100);
- };
- Collection.prototype.onstart = function (evt) {
- var self = this;
- var opts = self.options;
- evt = u.prepareEvent(evt);
- // Update the box position
- self.updateBox();
- var process = function (touch) {
- // If we can create new nipples
- // meaning we don't have more active nipples than we should.
- if (self.actives.length < opts.maxNumberOfNipples) {
- self.processOnStart(touch);
- }
- };
- u.map(evt, process);
- // We ask upstream to bind the document
- // on 'move' and 'end'
- self.manager.bindDocument();
- return false;
- };
- Collection.prototype.processOnStart = function (evt) {
- var self = this;
- var opts = self.options;
- var indexInIdles;
- var identifier = self.manager.getIdentifier(evt);
- var pressure = evt.force || evt.pressure || evt.webkitForce || 0;
- var position = {
- x: evt.pageX,
- y: evt.pageY
- };
- var nipple = self.getOrCreate(identifier, position);
- // Update its touch identifier
- nipple.identifier = identifier;
- var process = function (nip) {
- // Trigger the start.
- nip.trigger('start', nip);
- self.trigger('start ' + nip.id + ':start', nip);
- nip.show();
- if (pressure > 0) {
- self.pressureFn(evt, nip, nip.identifier);
- }
- // Trigger the first move event.
- self.processOnMove(evt);
- };
- // Transfer it from idles to actives.
- if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {
- self.idles.splice(indexInIdles, 1);
- }
- // Store the nipple in the actives array
- self.actives.push(nipple);
- self.ids.push(nipple.identifier);
- if (opts.mode !== 'semi') {
- process(nipple);
- } else {
- // In semi we check the distance of the touch
- // to decide if we have to reset the nipple
- var distance = u.distance(position, nipple.position);
- if (distance <= opts.catchDistance) {
- process(nipple);
- } else {
- nipple.destroy();
- self.processOnStart(evt);
- return;
- }
- }
- return nipple;
- };
- Collection.prototype.getOrCreate = function (identifier, position) {
- var self = this;
- var opts = self.options;
- var nipple;
- // If we're in static or semi, we might already have an active.
- if (/(semi|static)/.test(opts.mode)) {
- // Get the active one.
- // TODO: Multi-touche for semi and static will start here.
- // Return the nearest one.
- nipple = self.idles[0];
- if (nipple) {
- self.idles.splice(0, 1);
- return nipple;
- }
- if (opts.mode === 'semi') {
- // If we're in semi mode, we need to create one.
- return self.createNipple(position, identifier);
- }
- console.warn('Coudln\'t find the needed nipple.');
- return false;
- }
- // In dynamic, we create a new one.
- nipple = self.createNipple(position, identifier);
- return nipple;
- };
- Collection.prototype.processOnMove = function (evt) {
- var self = this;
- var opts = self.options;
- var identifier = self.manager.getIdentifier(evt);
- var nipple = self.nipples.get(identifier);
- if (!nipple) {
- // This is here just for safety.
- // It shouldn't happen.
- console.error('Found zombie joystick with ID ' + identifier);
- self.manager.removeIdentifier(identifier);
- return;
- }
- nipple.identifier = identifier;
- var size = nipple.options.size / 2;
- var pos = {
- x: evt.pageX,
- y: evt.pageY
- };
- var dist = u.distance(pos, nipple.position);
- var angle = u.angle(pos, nipple.position);
- var rAngle = u.radians(angle);
- var force = dist / size;
- // If distance is bigger than nipple's size
- // we clamp the position.
- if (dist > size) {
- dist = size;
- pos = u.findCoord(nipple.position, dist, angle);
- }
- nipple.frontPosition = {
- x: pos.x - nipple.position.x,
- y: pos.y - nipple.position.y
- };
- if (!opts.dataOnly) {
- u.applyPosition(nipple.ui.front, nipple.frontPosition);
- }
- // Prepare event's datas.
- var toSend = {
- identifier: nipple.identifier,
- position: pos,
- force: force,
- pressure: evt.force || evt.pressure || evt.webkitForce || 0,
- distance: dist,
- angle: {
- radian: rAngle,
- degree: angle
- },
- instance: nipple
- };
- // Compute the direction's datas.
- toSend = nipple.computeDirection(toSend);
- // Offset angles to follow units circle.
- toSend.angle = {
- radian: u.radians(180 - angle),
- degree: 180 - angle
- };
- // Send everything to everyone.
- nipple.trigger('move', toSend);
- self.trigger('move ' + nipple.id + ':move', toSend);
- };
- Collection.prototype.processOnEnd = function (evt) {
- var self = this;
- var opts = self.options;
- var identifier = self.manager.getIdentifier(evt);
- var nipple = self.nipples.get(identifier);
- var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);
- if (!nipple) {
- return;
- }
- if (!opts.dataOnly) {
- nipple.hide(function () {
- if (opts.mode === 'dynamic') {
- nipple.trigger('removed', nipple);
- self.trigger('removed ' + nipple.id + ':removed', nipple);
- self.manager
- .trigger('removed ' + nipple.id + ':removed', nipple);
- nipple.destroy();
- }
- });
- }
- // Clear the pressure interval reader
- clearInterval(self.pressureIntervals[nipple.identifier]);
- // Reset the direciton of the nipple, to be able to trigger a new direction
- // on start.
- nipple.resetDirection();
- nipple.trigger('end', nipple);
- self.trigger('end ' + nipple.id + ':end', nipple);
- // Remove identifier from our bank.
- if (self.ids.indexOf(nipple.identifier) >= 0) {
- self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
- }
- // Clean our actives array.
- if (self.actives.indexOf(nipple) >= 0) {
- self.actives.splice(self.actives.indexOf(nipple), 1);
- }
- if (/(semi|static)/.test(opts.mode)) {
- // Transfer nipple from actives to idles
- // if we're in semi or static mode.
- self.idles.push(nipple);
- } else if (self.nipples.indexOf(nipple) >= 0) {
- // Only if we're not in semi or static mode
- // we can remove the instance.
- self.nipples.splice(self.nipples.indexOf(nipple), 1);
- }
- // We unbind move and end.
- self.manager.unbindDocument();
- // We add back the identifier of the idle nipple;
- if (/(semi|static)/.test(opts.mode)) {
- self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;
- }
- };
- // Remove destroyed nipple from the lists
- Collection.prototype.onDestroyed = function(evt, nipple) {
- var self = this;
- if (self.nipples.indexOf(nipple) >= 0) {
- self.nipples.splice(self.nipples.indexOf(nipple), 1);
- }
- if (self.actives.indexOf(nipple) >= 0) {
- self.actives.splice(self.actives.indexOf(nipple), 1);
- }
- if (self.idles.indexOf(nipple) >= 0) {
- self.idles.splice(self.idles.indexOf(nipple), 1);
- }
- if (self.ids.indexOf(nipple.identifier) >= 0) {
- self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
- }
- // Remove the identifier from our bank
- self.manager.removeIdentifier(nipple.identifier);
- // We unbind move and end.
- self.manager.unbindDocument();
- };
- // Cleanly destroy the manager
- Collection.prototype.destroy = function () {
- var self = this;
- self.unbindEvt(self.options.zone, 'start');
- // Destroy nipples.
- self.nipples.forEach(function(nipple) {
- nipple.destroy();
- });
- // Clean 3DTouch intervals.
- for (var i in self.pressureIntervals) {
- if (self.pressureIntervals.hasOwnProperty(i)) {
- clearInterval(self.pressureIntervals[i]);
- }
- }
- // Notify the manager passing the instance
- self.trigger('destroyed', self.nipples);
- // We unbind move and end.
- self.manager.unbindDocument();
- // Unbind everything.
- self.off();
- };
- /* global u, Super, Collection */
- ///////////////////////
- /// MANAGER ///
- ///////////////////////
- function Manager (options) {
- var self = this;
- self.ids = {};
- self.index = 0;
- self.collections = [];
- self.config(options);
- self.prepareCollections();
- // Listen for resize, to reposition every joysticks
- var resizeTimer;
- u.bindEvt(window, 'resize', function (evt) {
- clearTimeout(resizeTimer);
- resizeTimer = setTimeout(function () {
- var pos;
- var scroll = u.getScroll();
- self.collections.forEach(function (collection) {
- collection.forEach(function (nipple) {
- pos = nipple.el.getBoundingClientRect();
- nipple.position = {
- x: scroll.x + pos.left,
- y: scroll.y + pos.top
- };
- });
- });
- }, 100);
- });
- return self.collections;
- };
- Manager.prototype = new Super();
- Manager.constructor = Manager;
- Manager.prototype.prepareCollections = function () {
- var self = this;
- // Public API Preparation.
- self.collections.create = self.create.bind(self);
- // Listen to anything
- self.collections.on = self.on.bind(self);
- // Unbind general events
- self.collections.off = self.off.bind(self);
- // Destroy everything
- self.collections.destroy = self.destroy.bind(self);
- // Get any nipple
- self.collections.get = function (id) {
- var nipple;
- self.collections.every(function (collection) {
- if (nipple = collection.get(id)) {
- return false;
- }
- return true;
- });
- return nipple;
- };
- };
- Manager.prototype.create = function (options) {
- return this.createCollection(options);
- };
- // Collection Factory
- Manager.prototype.createCollection = function (options) {
- var self = this;
- var collection = new Collection(self, options);
- self.bindCollection(collection);
- self.collections.push(collection);
- return collection;
- };
- Manager.prototype.bindCollection = function (collection) {
- var self = this;
- var type;
- // Bubble up identified events.
- var handler = function (evt, data) {
- // Identify the event type with the nipple's identifier.
- type = evt.type + ' ' + data.id + ':' + evt.type;
- self.trigger(type, data);
- };
- // When it gets destroyed we clean.
- collection.on('destroyed', self.onDestroyed.bind(self));
- // Other events that will get bubbled up.
- collection.on('shown hidden rested dir plain', handler);
- collection.on('dir:up dir:right dir:down dir:left', handler);
- collection.on('plain:up plain:right plain:down plain:left', handler);
- };
- Manager.prototype.bindDocument = function () {
- var self = this;
- // Bind only if not already binded
- if (!self.binded) {
- self.bindEvt(document, 'move')
- .bindEvt(document, 'end');
- self.binded = true;
- }
- };
- Manager.prototype.unbindDocument = function (force) {
- var self = this;
- // If there are no touch left
- // unbind the document.
- if (!Object.keys(self.ids).length || force === true) {
- self.unbindEvt(document, 'move')
- .unbindEvt(document, 'end');
- self.binded = false;
- }
- };
- Manager.prototype.getIdentifier = function (evt) {
- var id;
- // If no event, simple increment
- if (!evt) {
- id = this.index;
- } else {
- // Extract identifier from event object.
- // Unavailable in mouse events so replaced by latest increment.
- id = evt.identifier === undefined ? evt.pointerId : evt.identifier;
- if (id === undefined) {
- id = this.latest || 0;
- }
- }
- if (this.ids[id] === undefined) {
- this.ids[id] = this.index;
- this.index += 1;
- }
- // Keep the latest id used in case we're using an unidentified mouseEvent
- this.latest = id;
- return this.ids[id];
- };
- Manager.prototype.removeIdentifier = function (identifier) {
- var removed = {};
- for (var id in this.ids) {
- if (this.ids[id] === identifier) {
- removed.id = id;
- removed.identifier = this.ids[id];
- delete this.ids[id];
- break;
- }
- }
- return removed;
- };
- Manager.prototype.onmove = function (evt) {
- var self = this;
- self.onAny('move', evt);
- return false;
- };
- Manager.prototype.onend = function (evt) {
- var self = this;
- self.onAny('end', evt);
- return false;
- };
- Manager.prototype.onAny = function (which, evt) {
- var self = this;
- var id;
- var processFn = 'processOn' + which.charAt(0).toUpperCase() +
- which.slice(1);
- evt = u.prepareEvent(evt);
- var processColl = function (e, id, coll) {
- if (coll.ids.indexOf(id) >= 0) {
- coll[processFn](e);
- // Mark the event to avoid cleaning it later.
- e._found_ = true;
- }
- };
- var processEvt = function (e) {
- id = self.getIdentifier(e);
- u.map(self.collections, processColl.bind(null, e, id));
- // If the event isn't handled by any collection,
- // we need to clean its identifier.
- if (!e._found_) {
- self.removeIdentifier(id);
- }
- };
- u.map(evt, processEvt);
- return false;
- };
- // Cleanly destroy the manager
- Manager.prototype.destroy = function () {
- var self = this;
- self.unbindDocument(true);
- self.ids = {};
- self.index = 0;
- self.collections.forEach(function(collection) {
- collection.destroy();
- });
- self.off();
- };
- // When a collection gets destroyed
- // we clean behind.
- Manager.prototype.onDestroyed = function (evt, coll) {
- var self = this;
- if (self.collections.indexOf(coll) < 0) {
- return false;
- }
- self.collections.splice(self.collections.indexOf(coll), 1);
- };
- var factory = new Manager();
- return {
- create: function (options) {
- return factory.create(options);
- },
- factory: factory
- };
- });
|