micro-slider
Version:
Lightweight, Responsive, Touch Friendly, Dependency-Free JavaScript Slider with Hardware Accelerated Transitions.
762 lines (643 loc) • 25 kB
JavaScript
'use strict';
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
(function (MS) {
'use strict';
/**
* Support RequireJS and CommonJS/NodeJS module formats.
* Attach microSlider to the `window` when executed as a <script>.
* Cond 1: RequireJS
* Cond 2: CommonJS
* Cond 3: Attach to Window Object
*/
if (typeof define === 'function' && define.amd) {
define(MS);
} else if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && (typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') {
module.exports = MS();
} else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object') {
window.MicroSlider = MS();
}
})(function () {
'use strict';
var MicroSlider = function () {
function MicroSlider(container) {
var _this = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, MicroSlider);
this.prevHandler = function () {
var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : -1;
if (!_this.attached) {
return;
}
_this.target = _this.dim * Math.round(_this.offset / _this.dim) - _this.dim * n;
if (_this.offset !== _this.target) {
_this.amplitude = _this.target - _this.offset;
_this.timestamp = Date.now();
requestAnimationFrame(_this.autoScroll);
}
};
this.nextHandler = function () {
var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
if (!_this.attached) {
return;
}
_this.target = _this.dim * Math.round(_this.offset / _this.dim) + _this.dim * n;
if (_this.offset !== _this.target) {
_this.amplitude = _this.target - _this.offset;
_this.timestamp = Date.now();
requestAnimationFrame(_this.autoScroll);
}
};
this.clickHandler = function (e) {
if (!_this.attached) {
return;
}
var clickDelegate = e.target;
if (!clickDelegate.classList.contains(_this.options.sliderItemClass)) {
clickDelegate = e.target.closest('.' + _this.options.sliderItemClass);
if (!clickDelegate) {
return;
}
}
if (_this.draggedY) {
e.preventDefault();
e.stopPropagation();
return false;
} else if (!_this.options.fullWidth) {
var closest = clickDelegate.closest('.' + _this.options.sliderItemClass);
var clickedIndex = _this.getItemIndex(closest);
var diff = _this.center % _this.itemCount - clickedIndex;
if (diff !== 0) {
e.preventDefault();
e.stopPropagation();
}
_this.cycleTo(clickedIndex);
}
};
this.tapHandler = function (e) {
if (!_this.attached) {
return;
}
if (e.target === _this.sliderContainer || e.target.classList.contains(_this.options.sliderItemClass)) {
e.preventDefault();
}
_this.pressed = true;
_this.draggedX = false;
_this.draggedY = false;
_this.velocity = 0;
_this.amplitude = 0;
_this.frame = _this.offset;
_this.timestamp = Date.now();
_this.referencePos = {
x: _this.getXPos(e),
y: _this.getYPos(e)
};
clearInterval(_this.ticker);
_this.ticker = setInterval(_this.track, 100);
};
this.dragHandler = function (e) {
if (!_this.attached) {
return;
}
if (_this.pressed) {
var x = _this.getXPos(e);
var y = _this.getYPos(e);
var deltaX = _this.referencePos.x - x;
var deltaY = Math.abs(_this.referencePos.y - y);
if (deltaY < 30 && !_this.draggedX) {
if (deltaX > 2 || deltaX < -2) {
_this.draggedY = true;
_this.referencePos.x = x;
_this.scroll(_this.offset + deltaX);
}
} else if (_this.draggedY) {
e.preventDefault();
e.stopPropagation();
return false;
} else {
_this.draggedX = true;
}
}
if (_this.draggedY) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
this.releaseHandler = function (e) {
if (!_this.pressed) {
return;
}
_this.pressed = false;
clearInterval(_this.ticker);
_this.target = _this.offset;
if (_this.velocity > 10 || _this.velocity < -10) {
_this.amplitude = 0.9 * _this.velocity;
_this.target = _this.offset + _this.amplitude;
}
_this.target = Math.round(_this.target / _this.dim) * _this.dim;
if (_this.options.noWrap) {
if (_this.target >= _this.dim * (_this.itemCount - 1)) {
_this.target = _this.dim * (_this.itemCount - 1);
} else if (_this.target < 0) {
_this.target = 0;
}
}
_this.amplitude = _this.target - _this.offset;
_this.timestamp = Date.now();
requestAnimationFrame(_this.autoScroll);
if (_this.draggedY) {
e.preventDefault();
e.stopPropagation();
}
return false;
};
this.resizeHandler = function () {
if (!_this.attached) {
return;
}
if (_this.options.fullWidth) {
_this.setSliderDimensions();
_this.offset = _this.center * 2 * _this.itemDimensions.width;
_this.target = _this.offset;
} else {
_this.scroll();
}
};
this.track = function () {
var now = Date.now();
var elapsed = now - _this.timestamp;
_this.timestamp = now;
var delta = _this.offset - _this.frame;
_this.frame = _this.offset;
var v = 1000 * delta / (1 + elapsed);
_this.velocity = 0.8 * v + 0.2 * _this.velocity;
};
this.cycleTo = function (n) {
var c = _this.itemCount;
var diff = _this.center % c - n;
/**
* Account for Wraparound
*/
if (!_this.options.noWrap) {
if (diff < 0 && Math.abs(diff + c) < Math.abs(diff)) {
diff += c;
} else if (diff > 0 && Math.abs(diff - c) < diff) {
diff -= c;
}
}
/**
* Cycle to Next or Previous Item
*/
if (diff < 0) {
_this.nextHandler(Math.abs(diff));
} else if (diff > 0) {
_this.prevHandler(diff);
}
};
this.scroll = function () {
var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var el = void 0;
var i = 0;
var tweenOpacity = void 0;
var zTranslation = void 0;
_this.setScrollTimeout();
/**
* Compute Scroll
*/
_this.lastCenter = _this.center;
_this.offset = typeof x === 'number' ? x : _this.offset;
_this.center = Math.floor((_this.offset + _this.dim / 2) / _this.dim);
var delta = _this.offset - _this.center * _this.dim;
var dir = delta < 0 ? 1 : -1;
var tween = -dir * delta * 2 / _this.dim;
var half = _this.itemCount >> 1;
/**
* Center Item Positioning
*/
if (!_this.options.noWrap || _this.center >= 0 && _this.center < _this.itemCount) {
el = _this.items[_this.wrap(_this.center)];
_this.setActiveItem(el);
_this.renderTranslation(el, 0, _this.options.fullWidth ? 1 : 1 - 0.2 * tween, -delta / 2, _this.options.zoomScale * tween, dir * _this.options.shift * tween * i);
}
/**
* Iterate through all slider items and position them
*/
for (i = 1; i <= half; ++i) {
/**
* Right Items Positioning
*/
if (_this.options.fullWidth) {
zTranslation = _this.options.zoomScale;
tweenOpacity = i === half && delta < 0 ? 1 - tween : 1;
} else {
zTranslation = _this.options.zoomScale * (i * 2 + tween * dir);
tweenOpacity = 1 - 0.2 * (i * 2 + tween * dir);
}
if (!_this.options.noWrap || _this.center + i < _this.itemCount) {
el = _this.items[_this.wrap(_this.center + i)];
_this.renderTranslation(el, -i, tweenOpacity, _this.options.shift + (_this.dim * i - delta) / 2, zTranslation);
}
/**
* Left Items Positioning
*/
if (_this.options.fullWidth) {
zTranslation = _this.options.zoomScale;
tweenOpacity = i === half && delta > 0 ? 1 - tween : 1;
} else {
zTranslation = _this.options.zoomScale * (i * 2 - tween * dir);
tweenOpacity = 1 - 0.2 * (i * 2 - tween * dir);
}
/**
* Hide Wrapped Items
*/
if (!_this.options.noWrap || _this.center - i >= 0) {
el = _this.items[_this.wrap(_this.center - i)];
_this.renderTranslation(el, -i, tweenOpacity, -_this.options.shift + (-_this.dim * i - delta) / 2, zTranslation);
}
}
/**
* onCycleTo Callback
*/
if (_this.lastCenter !== _this.center && typeof _this.options.onCycleTo === 'function') {
_this.options.onCycleTo.call(_this, _this.activeItem, _this.draggedY);
}
};
this.setScrollTimeout = function () {
_this.scrolling = true;
if (_this.sliderContainer.classList.contains(_this.options.scrollingClass)) {
_this.sliderContainer.classList.add(_this.options.scrollingClass);
}
if (_this.scrollingTimeout != null) {
window.clearTimeout(_this.scrollingTimeout);
}
_this.scrollingTimeout = window.setTimeout(function () {
_this.scrolling = false;
_this.sliderContainer.classList.remove(_this.options.scrollingClass);
}, _this.options.transitionDuration);
};
this.setActiveItem = function (el) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
var item = null;
_this.items.forEach(function (i) {
if (i === el) {
item = i;
}
});
if (!el || !(el instanceof HTMLElement) || !item) {
throw new Error('Could not resolve element passed to `Slider.setActiveItem()`');
}
if (!el.classList.contains(_this.options.activeItemClass)) {
if (_this.activeItem !== null) {
_this.activeItem.classList.remove(_this.options.activeItemClass);
}
_this.activeItem = el;
_this.activeItemIndex = _this.getItemIndex(el);
if (!_this.activeItem.classList.contains(_this.options.activeItemClass)) {
_this.activeItem.classList.add(_this.options.activeItemClass);
}
}
_this.setActiveIndicator();
};
this.setActiveIndicator = function () {
if (_this.options.indicators && _this.activeIndicator !== _this.indicators[_this.activeItemIndex]) {
_this.activeIndicator.classList.remove(_this.options.indicatorActiveClass);
_this.activeIndicator = _this.indicators[_this.activeItemIndex];
_this.activeIndicator.classList.add(_this.options.indicatorActiveClass);
}
};
this.renderTranslation = function (el, zIndex, opacity, x1, z) {
var x2 = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
var alignment = 'translateX(0)';
if (!_this.options.fullWidth) {
var tX = (_this.sliderContainer.clientWidth - _this.itemDimensions.width) / 2;
var tY = (_this.sliderContainer.clientHeight - _this.itemDimensions.height) / 2;
alignment = 'translateX(' + tX + 'px) translateY(' + tY + 'px)';
}
var tx2 = '';
if (x2 !== null) {
tx2 = 'translateX(' + x2 + 'px) ';
}
el.style[_this.xForm] = alignment + ' translateX(' + x1 + 'px) ' + tx2 + 'translateZ(' + z + 'px)';
el.style.zIndex = zIndex;
el.style.opacity = opacity;
el.style.display = 'block';
};
this.autoScroll = function () {
var elapsed = Date.now() - _this.timestamp;
var delta = _this.amplitude * Math.exp(-elapsed / _this.options.transitionDuration);
if (!_this.amplitude) {
return false;
} else if (delta > 2 || delta < -2) {
_this.scroll(_this.target - delta);
requestAnimationFrame(_this.autoScroll);
} else {
_this.scroll(_this.target);
}
};
this.next = function () {
var i = _this.activeItemIndex + 1;
if (!_this.options.noWrap || _this.options.noWrap && i < _this.itemCount) {
_this.cycleTo(i);
}
};
this.prev = function () {
var i = _this.activeItemIndex - 1;
if (!_this.options.noWrap || _this.options.noWrap && i >= 0) {
_this.cycleTo(i);
}
};
this.set = function (n) {
return _this.cycleTo(n);
};
this.activeIndicator = null;
this.activeItem = null;
this.activeItemIndex = 0;
this.amplitude = null;
this.attached = false;
this.center = 0;
this.draggedX = false;
this.draggedY = false;
this.frame = null;
this.initialized = false;
this.lastCenter = null;
this.offset = 0;
this.options = _extends({}, MicroSlider.defaults, options);
this.pressed = false;
this.referencePos = { x: 0, y: 0 };
this.scrolling = false;
this.scrollingTimeout = null;
this.sliderContainer = container;
this.sliderWrapper = null;
this.target = 0;
this.ticker = null;
this.timestamp = null;
this.velocity = null;
this.init();
}
_createClass(MicroSlider, [{
key: 'init',
value: function init() {
this.setSliderContainer();
this.setSliderWrapper();
this.setSliderItems();
this.setSliderDimensions();
this.setSliderPerspective();
this.setIndicators();
this.setXForm();
this.bindEvents();
/**
* Trigger window resize event and manually scroll to finish initialization.
*/
this.refresh();
this.initialized = true;
}
}, {
key: 'setSliderContainer',
value: function setSliderContainer() {
if (typeof this.sliderContainer === 'string') {
this.sliderContainer = document.querySelector(this.sliderContainer);
}
if (!(this.sliderContainer instanceof HTMLElement)) {
throw new Error('\n The slider needs to be instantiated with an HTML Element as the first parameter or a valid CSS selector.\n eg.: new Carousel(document.getElementById(\'MyElement\')) or new Carousel(\'#myElement\').\n ');
}
if (!this.sliderContainer.classList.contains(this.options.sliderClass)) {
this.sliderContainer.classList.add(this.options.sliderClass);
}
if (!this.sliderContainer.classList.contains(this.options.initializedClass)) {
this.sliderContainer.classList.add(this.options.initializedClass);
}
}
}, {
key: 'setSliderWrapper',
value: function setSliderWrapper() {
this.sliderWrapper = document.createElement('div');
this.sliderWrapper.classList.add(this.options.sliderWrapperClass);
this.sliderWrapper.style.overflow = 'hidden';
this.sliderWrapper.style.width = '100%';
this.sliderContainer.appendChild(this.sliderWrapper);
}
}, {
key: 'setSliderItems',
value: function setSliderItems() {
var children = this.sliderContainer.children;
var dispose = [];
this.items = [];
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
var clone = child.cloneNode(true);
if (child.classList.contains(this.options.sliderItemClass)) {
if (child.classList.contains(this.options.activeItemClass)) {
this.activeItemIndex = i;
}
dispose.push(child);
this.items.push(clone);
this.sliderWrapper.appendChild(clone);
}
}
for (var _i = 0, _len = dispose.length; _i < _len; _i++) {
var d = dispose[_i];
d.parentNode.removeChild(d);
}
this.itemCount = this.items.length;
if (!this.itemCount) {
throw new Error('\n The slider does not contain any valid items. \n Please ensure that the items have the class name \'slider-item\' appended to them.\n ');
}
}
}, {
key: 'setSliderDimensions',
value: function setSliderDimensions() {
var item = this.items[0];
item.style.display = 'block';
this.setSliderItemsDimensions(item.offsetHeight + 'px', item.offsetWidth + 'px');
if (!this.initialized) {
item.style.display = 'none';
}
}
}, {
key: 'setSliderItemsDimensions',
value: function setSliderItemsDimensions() {
var height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '320px';
var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '320px';
for (var i = 0; i < this.itemCount; i++) {
var item = this.items[i];
item.style.height = height;
item.style.width = width;
}
this.itemDimensions = {
height: parseInt(height),
width: parseInt(width)
};
this.dim = this.itemDimensions.width * 2 + this.options.padding;
}
}, {
key: 'setSliderPerspective',
value: function setSliderPerspective() {
this.sliderWrapper.style.height = this.sliderContainer.offsetHeight + 'px';
this.sliderWrapper.style.perspective = this.options.fullWidth ? 'none' : this.itemDimensions.height * this.options.perspectiveFactor + 'px';
}
}, {
key: 'setIndicators',
value: function setIndicators() {
var _this2 = this;
if (this.options.indicators) {
this.indicators = [];
this.indicatorContainer = document.createElement(this.options.indicatorContainerTag);
this.indicatorContainer.className = this.options.indicatorContainerClass;
this.sliderContainer.appendChild(this.indicatorContainer);
var _loop = function _loop(i) {
var indicator = document.createElement(_this2.options.indicatorItemTag);
indicator.className = _this2.options.indicatorItemClass;
indicator.innerHTML = '<a href="#">' + _this2.options.indicatorText + '</a>';
if (i === 0) {
_this2.activeIndicator = indicator;
indicator.classList.add(_this2.options.indicatorActiveClass);
}
indicator.addEventListener('click', function (e) {
e.preventDefault();
_this2.set(i);
});
_this2.indicatorContainer.appendChild(indicator);
_this2.indicators.push(indicator);
};
for (var i = 0; i < this.itemCount; i++) {
_loop(i);
}
}
}
}, {
key: 'setXForm',
value: function setXForm() {
var xForm = 'transform';
['webkit', 'Moz', 'O', 'ms'].forEach(function (prefix) {
var e = prefix + 'Transform';
if (typeof document.body.style[e] !== 'undefined') {
xForm = e;
}
});
this.xForm = xForm;
}
}, {
key: 'bindEvents',
value: function bindEvents() {
var unbind = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var fn = unbind === false ? 'addEventListener' : 'removeEventListener';
/**
* Touch Events
*/
if (typeof window.ontouchstart !== 'undefined') {
this.sliderContainer[fn]('touchstart', this.tapHandler);
this.sliderContainer[fn]('touchmove', this.dragHandler);
this.sliderContainer[fn]('touchend', this.releaseHandler);
}
/**
* Mouse Events
*/
this.sliderContainer[fn]('mousedown', this.tapHandler);
this.sliderContainer[fn]('mousemove', this.dragHandler);
this.sliderContainer[fn]('mouseup', this.releaseHandler);
this.sliderContainer[fn]('mouseleave', this.releaseHandler);
this.sliderContainer[fn]('click', this.clickHandler);
/**
* Window Resize Event
*/
window[fn]('resize', this.resizeHandler);
this.attached = unbind === false;
}
}, {
key: 'getXPos',
value: function getXPos(e) {
var x = e.clientX;
if (e.targetTouches && e.targetTouches.length >= 1) {
x = e.targetTouches[0].clientX;
}
return x;
}
}, {
key: 'getYPos',
value: function getYPos(e) {
var y = e.clientY;
if (e.targetTouches && e.targetTouches.length >= 1) {
y = e.targetTouches[0].clientY;
}
return y;
}
}, {
key: 'wrap',
value: function wrap(x) {
var c = this.itemCount;
if (x >= c) {
return x % c;
} else if (x < 0) {
return this.wrap(c + x % c);
} else {
return x;
}
}
}, {
key: 'getItemIndex',
value: function getItemIndex(el) {
for (var i = 0; i < this.itemCount; i++) {
if (this.items[i] === el) {
return i;
}
}
return -1;
}
}, {
key: 'refresh',
value: function refresh() {
requestAnimationFrame(this.autoScroll);
this.resizeHandler();
this.scroll();
}
}, {
key: 'toggleFullWidth',
value: function toggleFullWidth() {
var fullWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var itemWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 320;
var itemHeight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var height = itemHeight === null ? this.itemDimensions.height + 'px' : itemHeight + 'px';
var width = fullWidth ? '100%' : itemWidth + 'px';
this.options.fullWidth = fullWidth;
this.setSliderItemsDimensions(height, width);
this.setSliderPerspective();
this.refresh();
}
}, {
key: 'detach',
value: function detach() {
this.bindEvents(true);
}
}]);
return MicroSlider;
}();
MicroSlider.defaults = {
activeItemClass: 'active',
fullWidth: false,
scrollingClass: 'scrolling',
indicators: false,
indicatorActiveClass: 'active',
indicatorContainerTag: 'ul',
indicatorContainerClass: 'indicators',
indicatorItemTag: 'li',
indicatorItemClass: 'indicator',
indicatorText: '•',
initializedClass: 'initialized',
noWrap: false,
onCycleTo: null,
padding: 0,
perspectiveFactor: 1.25,
shift: 0,
sliderClass: 'micro-slider',
sliderItemClass: 'slider-item',
sliderWrapperClass: 'slider-wrapper',
transitionDuration: 250,
zoomScale: -100
};
return MicroSlider;
});