Skip to content

Instantly share code, notes, and snippets.

@B1-0S
Last active April 16, 2021 17:42
Show Gist options
  • Save B1-0S/ec4e33a8c543705415cebbfc2ded053e to your computer and use it in GitHub Desktop.
Save B1-0S/ec4e33a8c543705415cebbfc2ded053e to your computer and use it in GitHub Desktop.
Slideout.JS with fixed Passive Listeners
(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