UNPKG

mand-mobile

Version:

A Vue.js 2.0 Mobile UI Toolkit

937 lines (802 loc) 34 kB
(function (global, factory) { if (typeof define === "function" && define.amd) { define(['exports', './index', './animate'], factory); } else if (typeof exports !== "undefined") { factory(exports, require('./index'), require('./animate')); } else { var mod = { exports: {} }; factory(mod.exports, global.index, global.animate); global.scroller = mod.exports; } })(this, function (exports, _index, _animate) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _animate2 = _interopRequireDefault(_animate); 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"); } } 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 members = { _isSingleTouch: false, _isTracking: false, _didDecelerationComplete: false, _isGesturing: false, _isDragging: false, _isDecelerating: false, _isAnimating: false, _clientLeft: 0, _clientTop: 0, _clientWidth: 0, _clientHeight: 0, _contentWidth: 0, _contentHeight: 0, _snapWidth: 100, _snapHeight: 100, _refreshHeight: null, _refreshActive: false, _refreshActivate: null, _refreshDeactivate: null, _refreshStart: null, _zoomLevel: 1, _scrollLeft: 0, _scrollTop: 0, _maxScrollLeft: 0, _maxScrollTop: 0, _scheduledLeft: 0, _scheduledTop: 0, _lastTouchLeft: null, _lastTouchTop: null, _lastTouchMove: null, _positions: null, _minDecelerationScrollLeft: null, _minDecelerationScrollTop: null, _maxDecelerationScrollLeft: null, _maxDecelerationScrollTop: null, _decelerationVelocityX: null, _decelerationVelocityY: null /* istanbul ignore next */ }; var Scroller = function () { function Scroller() { var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _index.noop; var options = arguments[1]; _classCallCheck(this, Scroller); this.options = { scrollingX: true, scrollingY: true, animating: true, animationDuration: 250, inRequestAnimationFrame: false, bouncing: true, locking: true, paging: false, snapping: false, snappingVelocity: 4, zooming: false, minZoom: 0.5, maxZoom: 3, speedMultiplier: 1, scrollingComplete: _index.noop, penetrationDeceleration: 0.03, penetrationAcceleration: 0.08 }; (0, _index.extend)(this.options, options); this._callback = callback; } /** * Configures the dimensions of the client (outer) and content (inner) elements. * Requires the available space for the outer element and the outer size of the inner element. * All values which are falsy (null or zero etc.) are ignored and the old value is kept. * * @param clientWidth {Integer ? null} Inner width of outer element * @param clientHeight {Integer ? null} Inner height of outer element * @param contentWidth {Integer ? null} Outer width of inner element * @param contentHeight {Integer ? null} Outer height of inner element */ _createClass(Scroller, [{ key: 'setDimensions', value: function setDimensions(clientWidth, clientHeight, contentWidth, contentHeight) { // Only update values which are defined if (clientWidth === +clientWidth) { this._clientWidth = clientWidth; } if (clientHeight === +clientHeight) { this._clientHeight = clientHeight; } if (contentWidth === +contentWidth) { this._contentWidth = contentWidth; } if (contentHeight === +contentHeight) { this._contentHeight = contentHeight; } // Refresh maximums this._computeScrollMax(); // Refresh scroll position this.scrollTo(this._scrollLeft, this._scrollTop, true); } }, { key: 'setPosition', value: function setPosition(left, top) { this._clientLeft = left || 0; this._clientTop = top || 0; } }, { key: 'setSnapSize', value: function setSnapSize(width, height) { this._snapWidth = width; this._snapHeight = height; } }, { key: 'getValues', value: function getValues() { return { left: this._scrollLeft, top: this._scrollTop, zoom: this._zoomLevel }; } }, { key: 'getScrollMax', value: function getScrollMax() { return { left: this._maxScrollLeft, top: this._maxScrollTop }; } }, { key: 'activatePullToRefresh', value: function activatePullToRefresh(height, activateCallback, deactivateCallback, startCallback) { this._refreshHeight = height; this._refreshActivate = activateCallback; this._refreshDeactivate = deactivateCallback; this._refreshStart = startCallback; } }, { key: 'triggerPullToRefresh', value: function triggerPullToRefresh() { // Use publish instead of scrollTo to allow scrolling to out of boundary position // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled this._publish(this._scrollLeft, -this._refreshHeight, this._zoomLevel, true); if (this._refreshStart) { this._refreshStart(); } } }, { key: 'finishPullToRefresh', value: function finishPullToRefresh() { this._refreshActive = false; if (this._refreshDeactivate) { this._refreshDeactivate(); } this.scrollTo(this._scrollLeft, this._scrollTop, true); } }, { key: 'scrollTo', value: function scrollTo(left, top, animate) { var zoom = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; // Stop deceleration if (this._isDecelerating) { _animate2.default.stop(this._isDecelerating); this._isDecelerating = false; } // Correct coordinates based on new zoom level if (zoom != null && zoom !== this._zoomLevel) { if (!this.options.zooming) { (0, _index.warn)('Zooming is not enabled!'); } zoom = zoom ? zoom : 1; left *= zoom; top *= zoom; // // Recompute maximum values while temporary tweaking maximum scroll ranges this._computeScrollMax(zoom); } else { // Keep zoom when not defined zoom = this._zoomLevel; } if (!this.options.scrollingX) { left = this._scrollLeft; } else { if (this.options.paging) { left = Math.round(left / this._clientWidth) * this._clientWidth; } else if (this.options.snapping) { left = Math.round(left / this._snapWidth) * this._snapWidth; } } if (!this.options.scrollingY) { top = this._scrollTop; } else { if (this.options.paging) { top = Math.round(top / this._clientHeight) * this._clientHeight; } else if (this.options.snapping) { top = Math.round(top / this._snapHeight) * this._snapHeight; } } // Limit for allowed ranges left = Math.max(Math.min(this._maxScrollLeft, left), 0); top = Math.max(Math.min(this._maxScrollTop, top), 0); // Don't animate when no change detected, still call publish to make sure // that rendered position is really in-sync with internal data if (left === this._scrollLeft && top === this._scrollTop) { animate = false; } // Publish new values if (!this._isTracking) { this._publish(left, top, zoom, animate); } } }, { key: 'zoomTo', value: function zoomTo(level, animate, originLeft, originTop, callback) { if (!this.options.zooming) { (0, _index.warn)('Zooming is not enabled!'); } // Add callback if exists if (callback) { this._zoomComplete = callback; } // Stop deceleration if (this._isDecelerating) { _animate2.default.stop(this._isDecelerating); this._isDecelerating = false; } var oldLevel = this._zoomLevel; // Normalize input origin to center of viewport if not defined if (originLeft == null) { originLeft = this._clientWidth / 2; } if (originTop == null) { originTop = this._clientHeight / 2; } // Limit level according to configuration level = Math.max(Math.min(level, this.options.maxZoom), this.options.minZoom); // Recompute maximum values while temporary tweaking maximum scroll ranges this._computeScrollMax(level); // Recompute left and top coordinates based on new zoom level var left = (originLeft + this._scrollLeft) * level / oldLevel - originLeft; var top = (originTop + this._scrollTop) * level / oldLevel - originTop; // Limit x-axis if (left > this._maxScrollLeft) { left = this._maxScrollLeft; } else if (left < 0) { left = 0; } // Limit y-axis if (top > this._maxScrollTop) { top = this._maxScrollTop; } else if (top < 0) { top = 0; } // Push values out this._publish(left, top, level, animate); } }, { key: 'doTouchStart', value: function doTouchStart(touches, timeStamp) { // Array-like check is enough here if (touches.length == null) { (0, _index.warn)('Invalid touch list: ' + touches); } if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf(); } if (typeof timeStamp !== 'number') { (0, _index.warn)('Invalid timestamp value: ' + timeStamp); } // Reset interruptedAnimation flag this._interruptedAnimation = true; // Stop deceleration if (this._isDecelerating) { _animate2.default.stop(this._isDecelerating); this._isDecelerating = false; this._interruptedAnimation = true; } // Stop animation if (this._isAnimating) { _animate2.default.stop(this._isAnimating); this._isAnimating = false; this._interruptedAnimation = true; } // Use center point when dealing with two fingers var isSingleTouch = touches.length === 1; var currentTouchLeft = void 0, currentTouchTop = void 0; if (isSingleTouch) { currentTouchLeft = touches[0].pageX; currentTouchTop = touches[0].pageY; } else { currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; } // Store initial positions this._initialTouchLeft = currentTouchLeft; this._initialTouchTop = currentTouchTop; // Store current zoom level this._zoomLevelStart = this._zoomLevel; // Store initial touch positions this._lastTouchLeft = currentTouchLeft; this._lastTouchTop = currentTouchTop; // Store initial move time stamp this._lastTouchMove = timeStamp; // Reset initial scale this._lastScale = 1; // Reset locking flags this._enableScrollX = !isSingleTouch && this.options.scrollingX; this._enableScrollY = !isSingleTouch && this.options.scrollingY; // Reset tracking flag this._isTracking = true; // Reset deceleration complete flag this._didDecelerationComplete = false; // Dragging starts directly with two fingers, otherwise lazy with an offset this._isDragging = !isSingleTouch; // Some features are disabled in multi touch scenarios this._isSingleTouch = isSingleTouch; // Clearing data structure this._positions = []; } }, { key: 'doTouchMove', value: function doTouchMove(touches, timeStamp, scale) { // Array-like check is enough here if (touches.length == null) { (0, _index.warn)('Invalid touch list: ' + touches); } if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf(); } if (typeof timeStamp !== 'number') { (0, _index.warn)('Invalid timestamp value: ' + timeStamp); } // Ignore event when tracking is not enabled (event might be outside of element) if (!this._isTracking) { return; } var currentTouchLeft = void 0, currentTouchTop = void 0; // Compute move based around of center of fingers if (touches.length === 2) { currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; } else { currentTouchLeft = touches[0].pageX; currentTouchTop = touches[0].pageY; } var positions = this._positions; // Are we already is dragging mode? if (this._isDragging) { // Compute move distance var moveX = currentTouchLeft - this._lastTouchLeft; var moveY = currentTouchTop - this._lastTouchTop; // Read previous scroll position and zooming var scrollLeft = this._scrollLeft; var scrollTop = this._scrollTop; var level = this._zoomLevel; // Work with scaling if (scale != null && this.options.zooming) { var oldLevel = level; // Recompute level based on previous scale and new scale level = level / this._lastScale * scale; // Limit level according to configuration level = Math.max(Math.min(level, this.options.maxZoom), this.options.minZoom); // Only do further compution when change happened if (oldLevel !== level) { // Compute relative event position to container var currentTouchLeftRel = currentTouchLeft - this._clientLeft; var currentTouchTopRel = currentTouchTop - this._clientTop; // Recompute left and top coordinates based on new zoom level scrollLeft = (currentTouchLeftRel + scrollLeft) * level / oldLevel - currentTouchLeftRel; scrollTop = (currentTouchTopRel + scrollTop) * level / oldLevel - currentTouchTopRel; // Recompute max scroll values this._computeScrollMax(level); } } if (this._enableScrollX) { scrollLeft -= moveX * this.options.speedMultiplier; var maxScrollLeft = this._maxScrollLeft; if (scrollLeft > maxScrollLeft || scrollLeft < 0) { // Slow down on the edges if (this.options.bouncing) { scrollLeft += moveX / 2 * this.options.speedMultiplier; } else if (scrollLeft > maxScrollLeft) { scrollLeft = maxScrollLeft; } else { scrollLeft = 0; } } } // Compute new vertical scroll position if (this._enableScrollY) { scrollTop -= moveY * this.options.speedMultiplier; var maxScrollTop = this._maxScrollTop; if (scrollTop > maxScrollTop || scrollTop < 0) { // Slow down on the edges if (this.options.bouncing) { scrollTop += moveY / 2 * this.options.speedMultiplier; // Support pull-to-refresh (only when only y is scrollable) if (!this._enableScrollX && this._refreshHeight != null) { if (!this._refreshActive && scrollTop <= -this._refreshHeight) { this._refreshActive = true; if (this._refreshActivate) { this._refreshActivate(); } } else if (this._refreshActive && scrollTop > -this._refreshHeight) { this._refreshActive = false; if (this._refreshDeactivate) { this._refreshDeactivate(); } } } } else if (scrollTop > maxScrollTop) { scrollTop = maxScrollTop; } else { scrollTop = 0; } } } // Keep list from growing infinitely (holding min 10, max 20 measure points) if (positions.length > 60) { positions.splice(0, 30); } // Track scroll movement for decleration positions.push(scrollLeft, scrollTop, timeStamp); // Sync scroll position this._publish(scrollLeft, scrollTop, level); // Otherwise figure out whether we are switching into dragging mode now. } else { var minimumTrackingForScroll = this.options.locking ? 3 : 0; var minimumTrackingForDrag = 5; var distanceX = Math.abs(currentTouchLeft - this._initialTouchLeft); var distanceY = Math.abs(currentTouchTop - this._initialTouchTop); this._enableScrollX = this.options.scrollingX && distanceX >= minimumTrackingForScroll; this._enableScrollY = this.options.scrollingY && distanceY >= minimumTrackingForScroll; positions.push(this._scrollLeft, this._scrollTop, timeStamp); this._isDragging = (this._enableScrollX || this._enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag); if (this._isDragging) { this._interruptedAnimation = false; } } // Update last touch positions and time stamp for next event this._lastTouchLeft = currentTouchLeft; this._lastTouchTop = currentTouchTop; this._lastTouchMove = timeStamp; } }, { key: 'doTouchEnd', value: function doTouchEnd(timeStamp) { if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf(); } if (typeof timeStamp !== 'number') { (0, _index.warn)('Invalid timestamp value: ' + timeStamp); } // Ignore event when tracking is not enabled (no touchstart event on element) // This is required as this listener ('touchmove') sits on the document and not on the element itthis. if (!this._isTracking) { return; } // Not touching anymore (when two finger hit the screen there are two touch end events) this._isTracking = false; // Be sure to reset the dragging flag now. Here we also detect whether // the finger has moved fast enough to switch into a deceleration animation. if (this._isDragging) { // Reset dragging flag this._isDragging = false; // Start deceleration // Verify that the last move detected was in some relevant time frame if (this._isSingleTouch && this.options.animating && timeStamp - this._lastTouchMove <= 100) { // Then figure out what the scroll position was about 100ms ago var positions = this._positions; var endPos = positions.length - 1; var startPos = endPos; // Move pointer to position measured 100ms ago for (var i = endPos; i > 0 && positions[i] > this._lastTouchMove - 100; i -= 3) { startPos = i; } // If start and stop position is identical in a 100ms timeframe, // we cannot compute any useful deceleration. if (startPos !== endPos) { // Compute relative movement between these two points var timeOffset = positions[endPos] - positions[startPos]; var movedLeft = this._scrollLeft - positions[startPos - 2]; var movedTop = this._scrollTop - positions[startPos - 1]; // Based on 50ms compute the movement to apply for each render step this._decelerationVelocityX = movedLeft / timeOffset * (1000 / 60); this._decelerationVelocityY = movedTop / timeOffset * (1000 / 60); // How much velocity is required to start the deceleration var minVelocityToStartDeceleration = this.options.paging || this.options.snapping ? this.options.snappingVelocity : 0.01; // Verify that we have enough velocity to start deceleration if (Math.abs(this._decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(this._decelerationVelocityY) > minVelocityToStartDeceleration) { // Deactivate pull-to-refresh when decelerating if (!this._refreshActive) { this._startDeceleration(timeStamp); } } else { this.options.scrollingComplete(); } } else { this.options.scrollingComplete(); } } else if (timeStamp - this._lastTouchMove > 100) { !this.options.snapping && this.options.scrollingComplete(); } } // If this was a slower move it is per default non decelerated, but this // still means that we want snap back to the bounds which is done here. // This is placed outside the condition above to improve edge case stability // e.g. touchend fired without enabled dragging. This should normally do not // have modified the scroll positions or even showed the scrollbars though. if (!this._isDecelerating) { if (this._refreshActive && this._refreshStart) { // Use publish instead of scrollTo to allow scrolling to out of boundary position // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled this._publish(this._scrollLeft, -this._refreshHeight, this._zoomLevel, true); if (this._refreshStart) { this._refreshStart(); } } else { if (this._interruptedAnimation || this._isDragging) { this.options.scrollingComplete(); } this.scrollTo(this._scrollLeft, this._scrollTop, true, this._zoomLevel); // Directly signalize deactivation (nothing todo on refresh?) if (this._refreshActive) { this._refreshActive = false; if (this._refreshDeactivate) { this._refreshDeactivate(); } } } } // Fully cleanup list this._positions.length = 0; } }, { key: '_publish', value: function _publish(left, top) { var _this = this; var zoom = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; var animate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation var wasAnimating = this._isAnimating; if (wasAnimating) { _animate2.default.stop(wasAnimating); this._isAnimating = false; } if (animate && this.options.animating) { // Keep scheduled positions for scrollBy/zoomBy functionality this._scheduledLeft = left; this._scheduledTop = top; this._scheduledZoom = zoom; var oldLeft = this._scrollLeft; var oldTop = this._scrollTop; var oldZoom = this._zoomLevel; var diffLeft = left - oldLeft; var diffTop = top - oldTop; var diffZoom = zoom - oldZoom; var step = function step(percent, now, render) { if (render) { _this._scrollLeft = oldLeft + diffLeft * percent; _this._scrollTop = oldTop + diffTop * percent; _this._zoomLevel = oldZoom + diffZoom * percent; // Push values out if (_this._callback) { _this._callback(_this._scrollLeft, _this._scrollTop, _this._zoomLevel); } } }; var verify = function verify(id) { return _this._isAnimating === id; }; var completed = function completed(renderedFramesPerSecond, animationId, wasFinished) { if (animationId === _this._isAnimating) { _this._isAnimating = false; } if (_this._didDecelerationComplete || wasFinished) { _this.options.scrollingComplete(); } if (_this.options.zooming) { _this._computeScrollMax(); if (_this._zoomComplete) { _this._zoomComplete(); _this._zoomComplete = null; } } }; var doAnimation = function doAnimation() { // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out _this._isAnimating = _animate2.default.start(step, verify, completed, _this.options.animationDuration, wasAnimating ? _animate.easeOutCubic : _animate.easeInOutCubic); }; if (this.options.inRequestAnimationFrame) { _animate2.default.requestAnimationFrame(function () { doAnimation(); }); } else { doAnimation(); } } else { this._scheduledLeft = this._scrollLeft = left; this._scheduledTop = this._scrollTop = top; this._scheduledZoom = this._zoomLevel = zoom; // Push values out if (this._callback) { this._callback(left, top, zoom); } // Fix max scroll ranges if (this.options.zooming) { this._computeScrollMax(); if (this._zoomComplete) { this._zoomComplete(); this._zoomComplete = null; } } } } }, { key: '_computeScrollMax', value: function _computeScrollMax(zoomLevel) { if (zoomLevel == null) { zoomLevel = this._zoomLevel; } this._maxScrollLeft = Math.max(this._contentWidth * zoomLevel - this._clientWidth, 0); this._maxScrollTop = Math.max(this._contentHeight * zoomLevel - this._clientHeight, 0); } }, { key: '_startDeceleration', value: function _startDeceleration(timeStamp) { var _this2 = this; if (this.options.paging) { var scrollLeft = Math.max(Math.min(this._scrollLeft, this._maxScrollLeft), 0); var scrollTop = Math.max(Math.min(this._scrollTop, this._maxScrollTop), 0); var clientWidth = this._clientWidth; var clientHeight = this._clientHeight; // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area. // Each page should have exactly the size of the client area. this._minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth; this._minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight; this._maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth; this._maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight; } else { this._minDecelerationScrollLeft = 0; this._minDecelerationScrollTop = 0; this._maxDecelerationScrollLeft = this._maxScrollLeft; this._maxDecelerationScrollTop = this._maxScrollTop; } // Wrap class method var step = function step(percent, now, render) { _this2._stepThroughDeceleration(render); }; // How much velocity is required to keep the deceleration running var minVelocityToKeepDecelerating = this.options.snapping ? this.options.snappingVelocity : 0.01; // Detect whether it's still worth to continue animating steps // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. var verify = function verify() { var shouldContinue = Math.abs(_this2._decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(_this2._decelerationVelocityY) >= minVelocityToKeepDecelerating; if (!shouldContinue) { _this2._didDecelerationComplete = true; } return shouldContinue; }; var completed = function completed(renderedFramesPerSecond, animationId, wasFinished) { _this2._isDecelerating = false; // if (this._didDecelerationComplete) { // this.options.scrollingComplete() // } // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions _this2.scrollTo(_this2._scrollLeft, _this2._scrollTop, _this2.options.snapping); }; // Start animation and switch on flag this._isDecelerating = _animate2.default.start(step, verify, completed); } }, { key: '_stepThroughDeceleration', value: function _stepThroughDeceleration(render) { // // COMPUTE NEXT SCROLL POSITION // // Add deceleration to scroll position var scrollLeft = this._scrollLeft + this._decelerationVelocityX; var scrollTop = this._scrollTop + this._decelerationVelocityY; // // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE // if (!this.options.bouncing) { var scrollLeftFixed = Math.max(Math.min(this._maxDecelerationScrollLeft, scrollLeft), this._minDecelerationScrollLeft); if (scrollLeftFixed !== scrollLeft) { scrollLeft = scrollLeftFixed; this._decelerationVelocityX = 0; } var scrollTopFixed = Math.max(Math.min(this._maxDecelerationScrollTop, scrollTop), this._minDecelerationScrollTop); if (scrollTopFixed !== scrollTop) { scrollTop = scrollTopFixed; this._decelerationVelocityY = 0; } } // // UPDATE SCROLL POSITION // if (render) { this._publish(scrollLeft, scrollTop, this._zoomLevel); } else { this._scrollLeft = scrollLeft; this._scrollTop = scrollTop; } // // SLOW DOWN // // Slow down velocity on every iteration if (!this.options.paging) { // This is the factor applied to every iteration of the animation // to slow down the process. This should emulate natural behavior where // objects slow down when the initiator of the movement is removed var frictionFactor = 0.95; this._decelerationVelocityX *= frictionFactor; this._decelerationVelocityY *= frictionFactor; } // // BOUNCING SUPPORT // if (this.options.bouncing) { var scrollOutsideX = 0; var scrollOutsideY = 0; // This configures the amount of change applied to deceleration/acceleration when reaching boundaries var penetrationDeceleration = this.options.penetrationDeceleration; var penetrationAcceleration = this.options.penetrationAcceleration; // Check limits if (scrollLeft < this._minDecelerationScrollLeft) { scrollOutsideX = this._minDecelerationScrollLeft - scrollLeft; } else if (scrollLeft > this._maxDecelerationScrollLeft) { scrollOutsideX = this._maxDecelerationScrollLeft - scrollLeft; } if (scrollTop < this._minDecelerationScrollTop) { scrollOutsideY = this._minDecelerationScrollTop - scrollTop; } else if (scrollTop > this._maxDecelerationScrollTop) { scrollOutsideY = this._maxDecelerationScrollTop - scrollTop; } // Slow down until slow enough, then flip back to snap position if (scrollOutsideX !== 0) { if (scrollOutsideX * this._decelerationVelocityX <= 0) { this._decelerationVelocityX += scrollOutsideX * penetrationDeceleration; } else { this._decelerationVelocityX = scrollOutsideX * penetrationAcceleration; } } if (scrollOutsideY !== 0) { if (scrollOutsideY * this._decelerationVelocityY <= 0) { this._decelerationVelocityY += scrollOutsideY * penetrationDeceleration; } else { this._decelerationVelocityY = scrollOutsideY * penetrationAcceleration; } } } } }]); return Scroller; }(); exports.default = Scroller; (0, _index.extend)(Scroller.prototype, members); });