UNPKG

hscroll

Version:
646 lines (542 loc) 17.2 kB
'use strict'; exports.__esModule = true; var _raf = require('raf'); var _raf2 = _interopRequireDefault(_raf); var _tween = require('tween'); var _tween2 = _interopRequireDefault(_tween); var _propDetect = require('prop-detect'); var _propDetect2 = _interopRequireDefault(_propDetect); var _events = require('events'); var _events2 = _interopRequireDefault(_events); var _emitter = require('emitter'); var _emitter2 = _interopRequireDefault(_emitter); var _computedStyle = require('computed-style'); var _computedStyle2 = _interopRequireDefault(_computedStyle); var _hasTouch = require('has-touch'); var _hasTouch2 = _interopRequireDefault(_hasTouch); var _debounce = require('debounce'); var _debounce2 = _interopRequireDefault(_debounce); var _mouseWheelEvent = require('mouse-wheel-event'); var _mouseWheelEvent2 = _interopRequireDefault(_mouseWheelEvent); var _resizelistener = require('resizelistener'); var _resizelistener2 = _interopRequireDefault(_resizelistener); var _removed = require('removed'); var _removed2 = _interopRequireDefault(_removed); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var has3d = _propDetect2['default'].has3d; var transform = _propDetect2['default'].transform; /** * Hscroll constructor * * @public * @param {Element} el * @param {Object} opt */ var Hscroll = function (_Emitter) { _inherits(Hscroll, _Emitter); function Hscroll(el) { var opt = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; _classCallCheck(this, Hscroll); var _this = _possibleConstructorReturn(this, _Emitter.call(this)); _this.el = el; el.style.overflow = 'hidden'; _this.loop = opt.loop || false; _this.interval = opt.interval || 1000; _this.duration = opt.duration || 300; _this.wrapper = _this.el.firstElementChild; if (!_this.wrapper) throw new Error('Child element required for hscroll'); _this.type = opt.type || 'normal'; // maximun duration in ms for fast swipe _this.threshold = opt.threshold || 200; // minimum moved distance for fast swipe _this.fastThreshold = opt.fastThreshold || 30; _this.autoWidth = opt.autoWidth || false; _this.autoHeight = opt.autoHeight || false; // transformX _this.tx = 0; _this.bind(); _this.refresh(); (0, _removed2['default'])(el, function () { _this.unbind(); }); return _this; } /** * Bind event handlers. * * @api public */ Hscroll.prototype.bind = function bind() { this.events = (0, _events2['default'])(this.wrapper, this); this.docEvents = (0, _events2['default'])(document, this); // standard mouse click events if (!_hasTouch2['default'] && document.addEventListener) { this.events.bind('mousedown', 'ontouchstart'); this.events.bind('mousemove', 'ontouchmove'); this.events.bind('mouseup', 'ontouchend'); this.docEvents.bind('mouseup', 'ontouchend'); this._wheelUnbind = (0, _mouseWheelEvent2['default'])(this.el, this.onwheel.bind(this), false); } else if (_hasTouch2['default']) { // W3C touch events this.events.bind('touchstart'); this.events.bind('touchmove'); this.docEvents.bind('touchend'); // MS IE touch events this.events.bind('PointerDown', 'ontouchstart'); this.events.bind('PointerMove', 'ontouchmove'); this.docEvents.bind('PointerUp', 'ontouchstart'); } this.unbindResize = (0, _resizelistener2['default'])(this.el, (0, _debounce2['default'])(this.refresh.bind(this), 100)); }; Hscroll.prototype.getTouch = function getTouch(e) { // "mouse" and "Pointer" events just use the event object itself var touch = e; if (e.touches && e.touches.length > 1) return; if (e.changedTouches && e.changedTouches.length > 0) { // W3C "touch" events use the `changedTouches` array touch = e.changedTouches[0]; } return touch; }; /** * Handle touchstart. * * @api private */ Hscroll.prototype.ontouchstart = function ontouchstart(e) { var _target = e.target || e.srcElement; if (this.animating) this.tween.stop(); var target = _target || e.srcElement; if (target.tagName.toLowerCase() == 'input') { if (/^(text|password|tel|search|number|email|url)$/.test(target.type) && target.value) return; } var touch = this.getTouch(e); if (!touch) return; this.start = this.curr(); this.speed = 0; var d = Date.now(); var sx = touch.pageX; var sy = touch.pageY; var self = this; var tx = this.tx; var limit = this.getLimitation(); var pad = 20; this.down = { x: sx, y: sy, tx: tx, at: d }; this.move = function (e, touch) { var cx = touch.pageX; var cy = touch.pageY; var px = self.previous ? self.previous.x : sx; var py = self.previous ? self.previous.y : sy; var leftOrRight = Math.abs(cx - px) > Math.abs(cy - py); if (!leftOrRight) return; e.preventDefault ? e.preventDefault() : e.returnValue = false; e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; self.calcuteSpeed(cx, cy); var tx = self.down.tx + cx - sx; tx = tx < limit.min - pad ? limit.min - pad : tx; tx = tx > limit.max + pad ? limit.max + pad : tx; self.setTransform(tx); }; }; /** * Handle touchmove. * * @api private */ Hscroll.prototype.ontouchmove = function ontouchmove(e) { var touch = this.getTouch(e); if (!touch || this.animating || !this.move) { this.move = null; return; } this.move(e, touch); }; /** * Handle touchend. * * @api private */ Hscroll.prototype.ontouchend = function ontouchend(e) { if (!this.move || !this.down || this.animating) return; this.move = null; var touch = this.getTouch(e); if (!touch) return; var t = Date.now(); var x = touch.pageX; var y = touch.pageY; var dx = Math.abs(x - this.down.x); var dy = Math.abs(y - this.down.y); if (dx > 5) { e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; e.stopImmediatePropagation ? e.stopImmediatePropagation() : void 0; } if (Math.sqrt(dx * dx + dy * dy) < 5) { this.emit('select', this.curr()); } if (this.type == 'swipe' && dx > dy && dx > this.fastThreshold && t - this.down.at < this.threshold) { // fast swipe var dir = x > this.down.x ? 1 : -1; this.show(this.start - dir); } else { if (this.type == 'swipe') { this.reset(); } else if (this.speed) { this.momentum(); } } this.down = this.previous = null; }; Hscroll.prototype.onwheel = function onwheel(dx, dy, dz, e) { if (Math.abs(dy) > Math.abs(dx)) return; e.preventDefault ? e.preventDefault() : e.returnValue = false; this.stop(); if (this.ts && !this.animating) { var speed = Math.abs(dx) / (Date.now() - this.ts); if (this.type == 'swipe' && speed > 2) this.swipe(dx < 0 ? 1 : -1); if (this.type == 'normal') { var tx = this.tx - dx; var limit = this.getLimitation(); tx = Math.max(limit.min, tx); tx = Math.min(limit.max, tx); this.setTransform(tx); } } this.ts = Date.now(); }; Hscroll.prototype.momentum = function momentum() { var deceleration = 0.001; var speed = this.speed; var x = this.tx; speed = Math.min(speed, 2); var limit = this.getLimitation(); var minX = limit.min; var rate = (4 - Math.PI) / 2; var destination = x + rate * (speed * speed) / (2 * deceleration) * this.direction; var duration = speed / deceleration; var newX = void 0; var ease = 'out-circ'; if (destination > 0) { newX = 0; ease = 'out-back'; } else if (destination < minX) { newX = minX; ease = 'out-back'; } if (typeof newX === 'number') { duration = duration * Math.abs((newX - x + 60) / (destination - x)); destination = newX; } if (x > 0 || x < minX) { duration = 500; ease = 'out-circ'; } if (this.type == 'fix') { var width = this.itemWidth; destination = Math.round(destination / width) * width; } this.animate(destination, duration, ease); return; }; /** * Unbind event handlers. * * @api public */ Hscroll.prototype.unbind = function unbind() { this.emit('ubind'); this.stop(); this.events.unbind(); this.docEvents.unbind(); this.unbindResize(); if (this._wheelUnbind) this._wheelUnbind(); }; /** * Show the previous item/slide, if any. * * @return {Swipe} self * @api public */ Hscroll.prototype.prev = function prev() { if (this.type == 'swipe') { return this.swipe(1); } else { return this.show(this.toFixed(1)); } }; /** * Show the next item/slide, if any. * * @return {Swipe} self * @api public */ Hscroll.prototype.next = function next() { if (this.type == 'swipe') { return this.swipe(-1); } else { return this.show(this.toFixed(-1)); } }; /** * Swipe to previous/next piece * * @public * @param {Number} dir 1 or -1 */ Hscroll.prototype.swipe = function swipe(dir) { var to = this.toFixed(dir); var self = this; var x = -to * this.viewWidth; if (x === this.tx) return Promise.resolve(null); return this.animate(x).then(function (stopped) { if (stopped) return; self.emit('show', to); }); }; /** * Get a sane item index from direction * * @private * @param {Number} dir * @returns {Number} */ Hscroll.prototype.toFixed = function toFixed(dir) { var to = this.curr() - dir; var max = this.type == 'swipe' ? this.itemCount - 1 : this.itemCount - Math.floor(this.viewWidth / this.itemWidth); if (to < 0) { to = this.loop ? max : 0; } else if (to > max) { to = this.loop ? 0 : max; } return to; }; /** * show nth item with scroll and animation * * @public * @param {Number} n * @param {Number} duration * @param {String} ease */ Hscroll.prototype.show = function show(n, duration, ease) { var _this2 = this; if (this.animating) this.tween.stop(); var width = this.type == 'swipe' ? this.viewWidth : this.itemWidth; n = Math.max(n, 0); n = Math.min(n, this.itemCount - 1); var tx = -n * width; var limit = this.getLimitation(); tx = Math.max(tx, limit.min); if (tx == this.tx) return Promise.resolve(null); var self = this; if (duration === 0) { this.setTransform(tx); this.refresh(); this.emit('show', n); return Promise.resolve(null); } return this.animate(tx, duration, ease).then(function (stopped) { if (stopped) return; _this2.refresh(); self.emit('show', n); }); }; /** * show last item with scroll * * @public * @param {Number} n */ Hscroll.prototype.last = function last() { return this.show(Infinity); }; /** * show first item with scroll * * @public * @param {Number} n */ Hscroll.prototype.first = function first() { return this.show(0); }; /** * autoplay like sliders * * @public */ Hscroll.prototype.play = function play() { var _this3 = this; if (this.playing) return; this.playing = true; if (this.inter != null) clearInterval(this.inter); this.inter = setInterval(function () { if (!_this3.playing) return; var curr = _this3.curr(); var max = _this3.type == 'swipe' ? _this3.itemCount - 1 : _this3.itemCount - Math.floor(_this3.viewWidth / _this3.itemWidth); if (curr >= max) { _this3.first(); } else { _this3.next(); } }, this.interval); }; /** * stop playing sliders * * @public */ Hscroll.prototype.stop = function stop() { this.playing = false; window.clearInterval(this.inter); }; /** * Restore to sane position * * @public */ Hscroll.prototype.reset = function reset() { var limit = this.getLimitation(); var tx = this.tx; if (tx < limit.min) { this.animate(limit.min); } else if (tx > limit.max) { this.animate(limit.max); } else if (this.type == 'swipe' && tx % this.viewWidth !== 0) { this.swipe(0); } }; /** * Get current item number * * @public * @returns {Number} */ Hscroll.prototype.curr = function curr() { if (this.type == 'swipe') { return Math.round(-this.tx / this.viewWidth); } else { return Math.round(-this.tx / this.itemWidth); } }; /** * Recalcute wrapper and set the position if invalid * * @public */ Hscroll.prototype.refresh = function refresh() { var parent = this.wrapper; this.viewWidth = this.el.clientWidth; if (this.viewWidth == 0) return; var items = parent.children; this.itemWidth = this.autoWidth ? this.viewWidth : items[0].clientWidth; if (this.autoWidth) { // set height and width for (var i = 0, l = items.length; i < l; i++) { if (this.autoWidth) items[i].style.width = this.viewWidth + 'px'; } } var item = items[this.curr()]; if (!item) return; var h = item.clientHeight; var pb = parseInt((0, _computedStyle2['default'])(parent, 'padding-bottom'), 10) || 0; if (this.autoHeight) parent.style.height = h + pb + 'px'; var width = items.length * this.itemWidth; parent.style.width = width + 'px'; if (this.type == 'swipe') { this.itemCount = Math.ceil(width / this.viewWidth); } else { this.itemCount = items.length; } this.reset(); }; /** * get min and max value for transform * * @private */ Hscroll.prototype.getLimitation = function getLimitation() { var max = void 0; if (this.type == 'swipe') { max = (this.itemCount - 1) * this.viewWidth; } else { max = parseInt((0, _computedStyle2['default'])(this.wrapper, 'width'), 10) - this.viewWidth; } return { max: 0, min: -max }; }; /** * set transform properties of element * * @public * @param {Number} x */ Hscroll.prototype.setTransform = function setTransform(x) { this.tx = x; if (typeof transform === 'string') { if (has3d) { this.wrapper.style[transform] = 'translate3d(' + x + 'px, 0, 0) '; } else { this.wrapper.style[transform] = 'translateX(' + x + 'px)'; } } else { // for old ie which have no transform this.wrapper.style.left = x + 'px'; } }; /** * Set translateX with animate duration (in milisecond) and ease * * @private * @param {Number} x * @param {Number} duration * @param {String} ease */ Hscroll.prototype.animate = function animate(x) { var duration = arguments.length <= 1 || arguments[1] === undefined ? this.duration : arguments[1]; var ease = arguments.length <= 2 || arguments[2] === undefined ? 'out-circ' : arguments[2]; var self = this; this.animating = true; var tween = this.tween = (0, _tween2['default'])({ x: this.tx }).ease(ease).to({ x: x }).duration(duration); tween.update(function (o) { self.setTransform(o.x); }); var promise = new Promise(function (resolve) { tween.on('end', function () { animate = function animate() {}; // eslint-disable-line self.animating = false; resolve(tween.stopped); }); }); function animate() { (0, _raf2['default'])(animate); tween.update(); } animate(); return promise; }; Hscroll.prototype.calcuteSpeed = function calcuteSpeed(x, y) { var previous = this.previous || this.down; var ts = Date.now(); var dt = ts - previous.at; if (ts - this.down.at < 100 || dt > 100) { var distance = Math.abs(x - previous.x); this.speed = distance / dt; this.direction = x > previous.x ? 1 : -1; } if (dt > 100) { this.previous = { x: x, y: y, at: ts }; } }; return Hscroll; }(_emitter2['default']); exports['default'] = Hscroll; module.exports = exports['default'];