Last active
April 16, 2021 17:42
-
-
Save B1-0S/ec4e33a8c543705415cebbfc2ded053e to your computer and use it in GitHub Desktop.
Slideout.JS with fixed Passive Listeners
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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.Slideout = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | |
'use strict'; | |
/** | |
* Module dependencies | |
*/ | |
var decouple = require('decouple'); | |
var Emitter = require('emitter'); | |
/** | |
* Privates | |
*/ | |
var scrollTimeout; | |
var scrolling = false; | |
var doc = window.document; | |
var html = doc.documentElement; | |
var msPointerSupported = window.navigator.msPointerEnabled; | |
var touch = { | |
'start': msPointerSupported ? 'MSPointerDown' : 'touchstart', | |
'move': msPointerSupported ? 'MSPointerMove' : 'touchmove', | |
'end': msPointerSupported ? 'MSPointerUp' : 'touchend' | |
}; | |
var prefix = (function prefix() { | |
var regex = /^(Webkit|Khtml|Moz|ms|O)(?=[A-Z])/; | |
var styleDeclaration = doc.getElementsByTagName('script')[0].style; | |
for (var prop in styleDeclaration) { | |
if (regex.test(prop)) { | |
return '-' + prop.match(regex)[0].toLowerCase() + '-'; | |
} | |
} | |
// Nothing found so far? Webkit does not enumerate over the CSS properties of the style object. | |
// However (prop in style) returns the correct value, so we'll have to test for | |
// the precence of a specific property | |
if ('WebkitOpacity' in styleDeclaration) { return '-webkit-'; } | |
if ('KhtmlOpacity' in styleDeclaration) { return '-khtml-'; } | |
return ''; | |
}()); | |
function extend(destination, from) { | |
for (var prop in from) { | |
if (from[prop]) { | |
destination[prop] = from[prop]; | |
} | |
} | |
return destination; | |
} | |
function inherits(child, uber) { | |
child.prototype = extend(child.prototype || {}, uber.prototype); | |
} | |
function hasIgnoredElements(el) { | |
while (el.parentNode) { | |
if (el.getAttribute('data-slideout-ignore') !== null) { | |
return el; | |
} | |
el = el.parentNode; | |
} | |
return null; | |
} | |
/** | |
* Slideout constructor | |
*/ | |
function Slideout(options) { | |
options = options || {}; | |
// Sets default values | |
this._startOffsetX = 0; | |
this._currentOffsetX = 0; | |
this._opening = false; | |
this._moved = false; | |
this._opened = false; | |
this._preventOpen = false; | |
// Sets panel | |
this.panel = options.panel; | |
this.menu = options.menu; | |
// Sets options | |
this._touch = options.touch === undefined ? true : options.touch && true; | |
this._side = options.side || 'left'; | |
this._easing = options.fx || options.easing || 'ease'; | |
this._duration = parseInt(options.duration, 10) || 300; | |
this._tolerance = parseInt(options.tolerance, 10) || 70; | |
this._padding = this._translateTo = parseInt(options.padding, 10) || 256; | |
this._orientation = this._side === 'right' ? -1 : 1; | |
this._translateTo *= this._orientation; | |
// Sets classnames | |
if (!this.panel.classList.contains('slideout-panel')) { | |
this.panel.classList.add('slideout-panel'); | |
} | |
if (!this.panel.classList.contains('slideout-panel-' + this._side)) { | |
this.panel.classList.add('slideout-panel-' + this._side); | |
} | |
if (!this.menu.classList.contains('slideout-menu')) { | |
this.menu.classList.add('slideout-menu'); | |
} | |
if (!this.menu.classList.contains('slideout-menu-' + this._side)) { | |
this.menu.classList.add('slideout-menu-' + this._side); | |
} | |
// Init touch events | |
if (this._touch) { | |
this._initTouchEvents(); | |
} | |
} | |
/** | |
* Inherits from Emitter | |
*/ | |
inherits(Slideout, Emitter); | |
/** | |
* Opens the slideout menu. | |
*/ | |
Slideout.prototype.open = function() { | |
var self = this; | |
this.emit('beforeopen'); | |
if (!html.classList.contains('slideout-open')) { | |
html.classList.add('slideout-open'); | |
} | |
this._setTransition(); | |
this._translateXTo(this._translateTo); | |
this._opened = true; | |
setTimeout(function() { | |
self.panel.style.transition = self.panel.style['-webkit-transition'] = ''; | |
self.emit('open'); | |
}, this._duration + 50); | |
return this; | |
}; | |
/** | |
* Closes slideout menu. | |
*/ | |
Slideout.prototype.close = function() { | |
var self = this; | |
if (!this.isOpen() && !this._opening) { | |
return this; | |
} | |
this.emit('beforeclose'); | |
this._setTransition(); | |
this._translateXTo(0); | |
this._opened = false; | |
setTimeout(function() { | |
html.classList.remove('slideout-open'); | |
self.panel.style.transition = self.panel.style['-webkit-transition'] = self.panel.style[prefix + 'transform'] = self.panel.style.transform = ''; | |
self.emit('close'); | |
}, this._duration + 50); | |
return this; | |
}; | |
/** | |
* Toggles (open/close) slideout menu. | |
*/ | |
Slideout.prototype.toggle = function() { | |
return this.isOpen() ? this.close() : this.open(); | |
}; | |
/** | |
* Returns true if the slideout is currently open, and false if it is closed. | |
*/ | |
Slideout.prototype.isOpen = function() { | |
return this._opened; | |
}; | |
/** | |
* Translates panel and updates currentOffset with a given X point | |
*/ | |
Slideout.prototype._translateXTo = function(translateX) { | |
this._currentOffsetX = translateX; | |
this.panel.style[prefix + 'transform'] = this.panel.style.transform = 'translateX(' + translateX + 'px)'; | |
return this; | |
}; | |
/** | |
* Set transition properties | |
*/ | |
Slideout.prototype._setTransition = function() { | |
this.panel.style[prefix + 'transition'] = this.panel.style.transition = prefix + 'transform ' + this._duration + 'ms ' + this._easing; | |
return this; | |
}; | |
/** | |
* Initializes touch event | |
*/ | |
Slideout.prototype._initTouchEvents = function() { | |
var self = this; | |
/** | |
* Decouple scroll event | |
*/ | |
this._onScrollFn = decouple(doc, 'scroll', function() { | |
if (!self._moved) { | |
clearTimeout(scrollTimeout); | |
scrolling = true; | |
scrollTimeout = setTimeout(function() { | |
scrolling = false; | |
}, 250); | |
} | |
}); | |
/** | |
* Prevents touchmove event if slideout is moving | |
*/ | |
this._preventMove = function(eve) { | |
if (self._moved) { | |
if (eve.cancelable) eve.preventDefault(); | |
} | |
}; | |
doc.addEventListener(touch.move, this._preventMove, {passive: false}); | |
/** | |
* Resets values on touchstart | |
*/ | |
this._resetTouchFn = function(eve) { | |
if (typeof eve.touches === 'undefined') { | |
return; | |
} | |
self._moved = false; | |
self._opening = false; | |
self._startOffsetX = eve.touches[0].pageX; | |
self._preventOpen = (!self._touch || (!self.isOpen() && self.menu.clientWidth !== 0)); | |
}; | |
this.panel.addEventListener(touch.start, this._resetTouchFn, {passive: true}); | |
/** | |
* Resets values on touchcancel | |
*/ | |
this._onTouchCancelFn = function() { | |
self._moved = false; | |
self._opening = false; | |
}; | |
this.panel.addEventListener('touchcancel', this._onTouchCancelFn, {passive: true}); | |
/** | |
* Toggles slideout on touchend | |
*/ | |
this._onTouchEndFn = function() { | |
if (self._moved) { | |
self.emit('translateend'); | |
(self._opening && Math.abs(self._currentOffsetX) > self._tolerance) ? self.open() : self.close(); | |
} | |
self._moved = false; | |
}; | |
this.panel.addEventListener(touch.end, this._onTouchEndFn, {passive: true}); | |
/** | |
* Translates panel on touchmove | |
*/ | |
this._onTouchMoveFn = function(eve) { | |
if ( | |
scrolling || | |
self._preventOpen || | |
typeof eve.touches === 'undefined' || | |
hasIgnoredElements(eve.target) | |
) { | |
return; | |
} | |
var dif_x = eve.touches[0].clientX - self._startOffsetX; | |
var translateX = self._currentOffsetX = dif_x; | |
if (Math.abs(translateX) > self._padding) { | |
return; | |
} | |
if (Math.abs(dif_x) > 20) { | |
self._opening = true; | |
var oriented_dif_x = dif_x * self._orientation; | |
if (self._opened && oriented_dif_x > 0 || !self._opened && oriented_dif_x < 0) { | |
return; | |
} | |
if (!self._moved) { | |
self.emit('translatestart'); | |
} | |
if (oriented_dif_x <= 0) { | |
translateX = dif_x + self._padding * self._orientation; | |
self._opening = false; | |
} | |
if (!(self._moved && html.classList.contains('slideout-open'))) { | |
html.classList.add('slideout-open'); | |
} | |
self.panel.style[prefix + 'transform'] = self.panel.style.transform = 'translateX(' + translateX + 'px)'; | |
self.emit('translate', translateX); | |
self._moved = true; | |
} | |
}; | |
this.panel.addEventListener(touch.move, this._onTouchMoveFn, {passive: true}); | |
return this; | |
}; | |
/** | |
* Enable opening the slideout via touch events. | |
*/ | |
Slideout.prototype.enableTouch = function() { | |
this._touch = true; | |
return this; | |
}; | |
/** | |
* Disable opening the slideout via touch events. | |
*/ | |
Slideout.prototype.disableTouch = function() { | |
this._touch = false; | |
return this; | |
}; | |
/** | |
* Destroy an instance of slideout. | |
*/ | |
Slideout.prototype.destroy = function() { | |
// Close before clean | |
this.close(); | |
// Remove event listeners | |
doc.removeEventListener(touch.move, this._preventMove); | |
this.panel.removeEventListener(touch.start, this._resetTouchFn); | |
this.panel.removeEventListener('touchcancel', this._onTouchCancelFn); | |
this.panel.removeEventListener(touch.end, this._onTouchEndFn); | |
this.panel.removeEventListener(touch.move, this._onTouchMoveFn); | |
doc.removeEventListener('scroll', this._onScrollFn); | |
// Remove methods | |
this.open = this.close = function() {}; | |
// Return the instance so it can be easily dereferenced | |
return this; | |
}; | |
/** | |
* Expose Slideout | |
*/ | |
module.exports = Slideout; | |
},{"decouple":2,"emitter":3}],2:[function(require,module,exports){ | |
'use strict'; | |
var requestAnimFrame = (function() { | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
function (callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
}()); | |
function decouple(node, event, fn) { | |
var eve, | |
tracking = false; | |
function captureEvent(e) { | |
eve = e; | |
track(); | |
} | |
function track() { | |
if (!tracking) { | |
requestAnimFrame(update); | |
tracking = true; | |
} | |
} | |
function update() { | |
fn.call(node, eve); | |
tracking = false; | |
} | |
node.addEventListener(event, captureEvent, false); | |
return captureEvent; | |
} | |
/** | |
* Expose decouple | |
*/ | |
module.exports = decouple; | |
},{}],3:[function(require,module,exports){ | |
"use strict"; | |
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; | |
exports.__esModule = true; | |
/** | |
* Creates a new instance of Emitter. | |
* @class | |
* @returns {Object} Returns a new instance of Emitter. | |
* @example | |
* // Creates a new instance of Emitter. | |
* var Emitter = require('emitter'); | |
* | |
* var emitter = new Emitter(); | |
*/ | |
var Emitter = (function () { | |
function Emitter() { | |
_classCallCheck(this, Emitter); | |
} | |
/** | |
* Adds a listener to the collection for the specified event. | |
* @memberof! Emitter.prototype | |
* @function | |
* @param {String} event - The event name. | |
* @param {Function} listener - A listener function to add. | |
* @returns {Object} Returns an instance of Emitter. | |
* @example | |
* // Add an event listener to "foo" event. | |
* emitter.on('foo', listener); | |
*/ | |
Emitter.prototype.on = function on(event, listener) { | |
// Use the current collection or create it. | |
this._eventCollection = this._eventCollection || {}; | |
// Use the current collection of an event or create it. | |
this._eventCollection[event] = this._eventCollection[event] || []; | |
// Appends the listener into the collection of the given event | |
this._eventCollection[event].push(listener); | |
return this; | |
}; | |
/** | |
* Adds a listener to the collection for the specified event that will be called only once. | |
* @memberof! Emitter.prototype | |
* @function | |
* @param {String} event - The event name. | |
* @param {Function} listener - A listener function to add. | |
* @returns {Object} Returns an instance of Emitter. | |
* @example | |
* // Will add an event handler to "foo" event once. | |
* emitter.once('foo', listener); | |
*/ | |
Emitter.prototype.once = function once(event, listener) { | |
var self = this; | |
function fn() { | |
self.off(event, fn); | |
listener.apply(this, arguments); | |
} | |
fn.listener = listener; | |
this.on(event, fn); | |
return this; | |
}; | |
/** | |
* Removes a listener from the collection for the specified event. | |
* @memberof! Emitter.prototype | |
* @function | |
* @param {String} event - The event name. | |
* @param {Function} listener - A listener function to remove. | |
* @returns {Object} Returns an instance of Emitter. | |
* @example | |
* // Remove a given listener. | |
* emitter.off('foo', listener); | |
*/ | |
Emitter.prototype.off = function off(event, listener) { | |
var listeners = undefined; | |
// Defines listeners value. | |
if (!this._eventCollection || !(listeners = this._eventCollection[event])) { | |
return this; | |
} | |
listeners.forEach(function (fn, i) { | |
if (fn === listener || fn.listener === listener) { | |
// Removes the given listener. | |
listeners.splice(i, 1); | |
} | |
}); | |
// Removes an empty event collection. | |
if (listeners.length === 0) { | |
delete this._eventCollection[event]; | |
} | |
return this; | |
}; | |
/** | |
* Execute each item in the listener collection in order with the specified data. | |
* @memberof! Emitter.prototype | |
* @function | |
* @param {String} event - The name of the event you want to emit. | |
* @param {...Object} data - Data to pass to the listeners. | |
* @returns {Object} Returns an instance of Emitter. | |
* @example | |
* // Emits the "foo" event with 'param1' and 'param2' as arguments. | |
* emitter.emit('foo', 'param1', 'param2'); | |
*/ | |
Emitter.prototype.emit = function emit(event) { | |
var _this = this; | |
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | |
args[_key - 1] = arguments[_key]; | |
} | |
var listeners = undefined; | |
// Defines listeners value. | |
if (!this._eventCollection || !(listeners = this._eventCollection[event])) { | |
return this; | |
} | |
// Clone listeners | |
listeners = listeners.slice(0); | |
listeners.forEach(function (fn) { | |
return fn.apply(_this, args); | |
}); | |
return this; | |
}; | |
return Emitter; | |
})(); | |
/** | |
* Exports Emitter | |
*/ | |
exports["default"] = Emitter; | |
module.exports = exports["default"]; | |
},{}]},{},[1])(1) | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment