diff --git a/lib/ftscroller.js b/lib/ftscroller.js index fb53762..7ce61e4 100755 --- a/lib/ftscroller.js +++ b/lib/ftscroller.js @@ -52,14 +52,12 @@ /*jslint nomen: true, vars: true, browser: true, node: true, es5: true, continue: true, white: true*/ /*globals FTScrollerOptions*/ -var FTScroller, CubicBezier; - (function () { 'use strict'; // Global flag to determine if any scroll is currently active. This prevents // issues when using multiple scrollers, particularly when they're nested. - var _ftscrollerMoving = false; + var _ftscrollerMoving = null; // Determine whether pointer events or touch events can be used var _trackPointerEvents = window.navigator.msPointerEnabled; @@ -159,8 +157,11 @@ var FTScroller, CubicBezier; * construct the scroller in, and any scrolling options. * Note that app-wide options can also be set using a global FTScrollerOptions * object. + * + * @constructor + * @expose */ - FTScroller = function (domNode, options) { + self.FTScroller = function FTScroller(domNode, options) { var key; var destroy, setSnapSize, scrollTo, scrollBy, updateDimensions, addEventListener, removeEventListener, _startScroll, _updateScroll, _endScroll, _finalizeScroll, _interruptScroll, _flingScroll, _snapScroll, _getSnapPositionForPosition, _initializeDOM, _existingDOMValid, _domChanged, _updateDimensions, _updateScrollbarDimensions, _updateElementPosition, _updateSegments, _setAxisPosition, _scheduleAxisPosition, _fireEvent, _childFocused, _modifyDistanceBeyondBounds, _distancesBeyondBounds, _startAnimation, _scheduleRender, _cancelAnimation, _toggleEventHandlers, _onTouchStart, _onTouchMove, _onTouchEnd, _onMouseDown, _onMouseMove, _onMouseUp, _onPointerDown, _onPointerMove, _onPointerUp, _onPointerCancel, _onPointerCaptureEnd, _onClick, _onMouseScroll, _captureInput, _releaseInputCapture, _getBoundingRect; @@ -173,89 +174,109 @@ var FTScroller, CubicBezier; var _instanceOptions = { // Whether to display scrollbars as appropriate + /** @expose */ scrollbars: true, // Enable scrolling on the X axis if content is available + /** @expose */ scrollingX: true, // Enable scrolling on the Y axis if content is available + /** @expose */ scrollingY: true, // The initial movement required to trigger a scroll, in pixels; this is the point at which // the scroll is exclusive to this particular FTScroller instance. + /** @expose */ scrollBoundary: 1, // The initial movement required to trigger a visual indication that scrolling is occurring, // in pixels. This is enforced to be less than or equal to the scrollBoundary, and is used to // define when the scroller starts drawing changes in response to an input, even if the scroll // is not treated as having begun/locked yet. + /** @expose */ scrollResponseBoundary: 1, // Whether to always enable scrolling, even if the content of the scroller does not // require the scroller to function. This makes the scroller behave more like an // element set to "overflow: scroll", with bouncing always occurring if enabled. + /** @expose */ alwaysScroll: false, // The content width to use when determining scroller dimensions. If this // is false, the width will be detected based on the actual content. + /** @expose */ contentWidth: undefined, // The content height to use when determining scroller dimensions. If this // is false, the height will be detected based on the actual content. + /** @expose */ contentHeight: undefined, // Enable snapping of content to 'pages' or a pixel grid + /** @expose */ snapping: false, // Define the horizontal interval of the pixel grid; snapping must be enabled for this to // take effect. If this is not defined, snapping will use intervals based on container size. + /** @expose */ snapSizeX: undefined, // Define the vertical interval of the pixel grid; snapping must be enabled for this to // take effect. If this is not defined, snapping will use intervals based on container size. + /** @expose */ snapSizeY: undefined, // Control whether snapping should be fully paginated, only ever flicking to the next page // and not beyond. Snapping needs to be enabled for this to take effect. + /** @expose */ paginatedSnap: false, // Allow scroll bouncing and elasticity near the ends and grid + /** @expose */ bouncing: true, // Automatically detects changes to the contained markup and // updates its dimensions whenever the content changes. This is // set to false if a contentWidth or contentHeight are supplied. + /** @expose */ updateOnChanges: true, // Automatically catches changes to the window size and updates // its dimensions. + /** @expose */ updateOnWindowResize: false, // The alignment to use if the content is smaller than the container; // this also applies to initial positioning of scrollable content. // Valid alignments are -1 (top or left), 0 (center), and 1 (bottom or right). + /** @expose */ baseAlignments: { x: -1, y: -1 }, // Whether to use a window scroll flag, eg window.foo, to control whether // to allow scrolling to start or now. If the window flag is set to true, // this element will not start scrolling; this element will also toggle // the variable while scrolling + /** @expose */ windowScrollingActiveFlag: undefined, // Instead of always using translate3d for transforms, a mix of translate3d // and translate with a hardware acceleration class used to trigger acceleration // is used; this is to allow CSS inheritance to be used to allow dynamic // disabling of backing layers on older platforms. + /** @expose */ hwAccelerationClass: 'ftscroller_hwaccelerated', // While use of requestAnimationFrame is highly recommended on platforms // which support it, it can result in the animation being a further half-frame // behind the input method, increasing perceived lag slightly. To disable this, // set this property to false. + /** @expose */ enableRequestAnimationFrameSupport: true, // Set the maximum time (ms) that a fling can take to complete; if // this is not set, flings will complete instantly + /** @expose */ maxFlingDuration: 1000 }; @@ -317,11 +338,11 @@ var FTScroller, CubicBezier; var _kDecelerateBezier = new CubicBezier(0, 0.5, 0.5, 1); // Allow certain events to be debounced - var _domChangeDebouncer = false; - var _scrollWheelEndDebouncer = false; + var _domChangeDebouncer = null; + var _scrollWheelEndDebouncer = null; // Performance switches on browsers supporting requestAnimationFrame - var _animationFrameRequest = false; + var _animationFrameRequest = 0; var _animationTargetPosition = { x: 0, y: 0 }; var _reqAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || false; var _cancelAnimationFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame || window.mozCancelAnimationFrame || window.mozCancelRequestAnimationFrame || window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.msCancelAnimationFrame || window.msCancelRequestAnimationFrame || false; @@ -400,7 +421,7 @@ var FTScroller, CubicBezier; _cancelAnimation(); if (_domChangeDebouncer) { window.clearTimeout(_domChangeDebouncer); - _domChangeDebouncer = false; + _domChangeDebouncer = null; } for (i = 0, l = _timeouts.length; i < l; i = i + 1) { window.clearTimeout(_timeouts[i]); @@ -430,7 +451,7 @@ var FTScroller, CubicBezier; // If this is currently tracked as a scrolling instance, clear the flag if (_ftscrollerMoving && _ftscrollerMoving === _self) { - _ftscrollerMoving = false; + _ftscrollerMoving = null; if (_instanceOptions.windowScrollingActiveFlag) { window[_instanceOptions.windowScrollingActiveFlag] = false; } @@ -644,6 +665,12 @@ var FTScroller, CubicBezier; /** * Continue a scroll as a result of an updated position + * + * @param {number} inputX + * @param {number} inputY + * @param {number} inputTime + * @param {MouseEvent} rawEvent + * @param {boolean} [scrollInterrupt] */ _updateScroll = function _updateScroll(inputX, inputY, inputTime, rawEvent, scrollInterrupt) { var axis, otherScrollerActive; @@ -791,6 +818,9 @@ var FTScroller, CubicBezier; * Complete a scroll with a final event time if available (it may * not be, depending on the input type); this may continue the scroll * with a fling and/or bounceback depending on options. + * + * @param {number} inputTime + * @param {MouseEvent} [rawEvent] */ _endScroll = function _endScroll(inputTime, rawEvent) { _inputIdentifier = false; @@ -829,6 +859,8 @@ var FTScroller, CubicBezier; /** * Remove the scrolling class, cleaning up display. + * + * @param {boolean} [scrollCancelled] */ _finalizeScroll = function _finalizeScroll(scrollCancelled) { var i, l, axis; @@ -1158,6 +1190,8 @@ var FTScroller, CubicBezier; /** * Bounce back into bounds if necessary, or snap to a grid location. + * + * @param {boolean} [scrollCancelled] */ _snapScroll = function _snapScroll(scrollCancelled) { var axis; @@ -1384,6 +1418,9 @@ var FTScroller, CubicBezier; }, 100); }; + /** + * @param {boolean} [ignoreSnapScroll] + */ _updateDimensions = function _updateDimensions(ignoreSnapScroll) { var axis; @@ -1395,7 +1432,7 @@ var FTScroller, CubicBezier; if (_domChangeDebouncer) { window.clearTimeout(_domChangeDebouncer); - _domChangeDebouncer = false; + _domChangeDebouncer = null; } var containerWidth, containerHeight, startAlignments; @@ -1424,7 +1461,7 @@ var FTScroller, CubicBezier; var rawScrollHeight = options.contentHeight || _contentParentNode.offsetHeight; var scrollWidth = rawScrollWidth; var scrollHeight = rawScrollHeight; - var targetPosition = { x: false, y: false }; + var targetPosition = { x: -1, y: -1 }; // Update snap grid if (!_snapGridSize.userX) { @@ -1488,11 +1525,11 @@ var FTScroller, CubicBezier; } } } - if (_instanceOptions.scrollingX && targetPosition.x !== false) { + if (_instanceOptions.scrollingX && targetPosition.x !== -1) { _setAxisPosition('x', targetPosition.x, 0); _baseScrollPosition.x = targetPosition.x; } - if (_instanceOptions.scrollingY && targetPosition.y !== false) { + if (_instanceOptions.scrollingY && targetPosition.y !== -1) { _setAxisPosition('y', targetPosition.y, 0); _baseScrollPosition.y = targetPosition.y; } @@ -1582,6 +1619,13 @@ var FTScroller, CubicBezier; } }; + /** + * @param {string} axis + * @param {number} position + * @param {number} [animationDuration] + * @param {CubicBezier} [animationBezier] + * @param {number} [boundsCrossDelay] + */ _setAxisPosition = function _setAxisPosition(axis, position, animationDuration, animationBezier, boundsCrossDelay) { var transitionCSSString, newPositionAtExtremity = null; @@ -1659,7 +1703,7 @@ var FTScroller, CubicBezier; _eventListeners[eventName][i](eventObject); } catch (error) { if (window.console && window.console.error) { - window.console.error(error.message + ' (' + error.sourceURL + ', line ' + error.line + ')'); + window.console.error(error.message + ' (' + error.sourceURL + ', line ' + error.lineNumber + ')'); } } } @@ -1741,7 +1785,7 @@ var FTScroller, CubicBezier; _startAnimation = function _startAnimation() { if (_reqAnimationFrame) { _cancelAnimation(); - _animationFrameRequest = _reqAnimationFrame(_scheduleRender); + _animationFrameRequest = _reqAnimationFrame.call(window, _scheduleRender); } }; @@ -1755,7 +1799,7 @@ var FTScroller, CubicBezier; var axis; // Request the next update at once - _animationFrameRequest = _reqAnimationFrame(_scheduleRender); + _animationFrameRequest = _reqAnimationFrame.call(window, _scheduleRender); // Perform the draw. for (axis in _scrollableAxes) { @@ -1769,12 +1813,12 @@ var FTScroller, CubicBezier; * Stops the animation process. */ _cancelAnimation = function _cancelAnimation() { - if (_animationFrameRequest === false || !_cancelAnimationFrame) { + if (!_animationFrameRequest || !_cancelAnimationFrame) { return; } - _cancelAnimationFrame(_animationFrameRequest); - _animationFrameRequest = false; + _cancelAnimationFrame.call(window, _animationFrameRequest); + _animationFrameRequest = 0; }; /** @@ -2207,7 +2251,7 @@ var FTScroller, CubicBezier; */ FTScroller.prototype.getPrependedHTML = function (excludeXAxis, excludeYAxis, hwAccelerationClass) { if (!hwAccelerationClass) { - if (typeof FTScrollerOptions === 'object' && FTScrollerOptions.hwAccelerationClass) { + if (typeof FTScrollerOptions === 'object' && /** @expose */ FTScrollerOptions.hwAccelerationClass) { hwAccelerationClass = FTScrollerOptions.hwAccelerationClass; } else { hwAccelerationClass = 'ftscroller_hwaccelerated'; @@ -2257,11 +2301,6 @@ var FTScroller, CubicBezier; return output; }; -}()); - - -(function () { - 'use strict'; /** * Represents a two-dimensional cubic bezier curve with the starting @@ -2269,25 +2308,31 @@ var FTScroller, CubicBezier; * have x and y coordinates between 0 and 1. * * This type of bezier curves can be used as CSS transform timing functions. + * + * @constructor + * @param {number} [p1x] + * @param {number} [p1y] + * @param {number} [p2x] + * @param {number} [p2y] */ - CubicBezier = function (p1x, p1y, p2x, p2y) { + function CubicBezier(p1x, p1y, p2x, p2y) { if (!(p1x >= 0 && p1x <= 1)) { - throw new RangeError('"p1x" must be a number between 0 and 1. ' + 'Got ' + p1x + 'instead.'); + throw new RangeError('"p1x" must be a number between 0 and 1. ' + 'Got ' + p1x + ' instead.'); } if (!(p1y >= 0 && p1y <= 1)) { - throw new RangeError('"p1y" must be a number between 0 and 1. ' + 'Got ' + p1y + 'instead.'); + throw new RangeError('"p1y" must be a number between 0 and 1. ' + 'Got ' + p1y + ' instead.'); } if (!(p2x >= 0 && p2x <= 1)) { - throw new RangeError('"p2x" must be a number between 0 and 1. ' + 'Got ' + p2x + 'instead.'); + throw new RangeError('"p2x" must be a number between 0 and 1. ' + 'Got ' + p2x + ' instead.'); } if (!(p2y >= 0 && p2y <= 1)) { - throw new RangeError('"p2y" must be a number between 0 and 1. ' + 'Got ' + p2y + 'instead.'); + throw new RangeError('"p2y" must be a number between 0 and 1. ' + 'Got ' + p2y + ' instead.'); } // Control points this._p1 = { x: p1x, y: p1y }; this._p2 = { x: p2x, y: p2y }; - }; + } CubicBezier.prototype._getCoordinateForT = function (t, p1, p2) { var c = 3 * p1, @@ -2457,7 +2502,7 @@ var FTScroller, CubicBezier; * t === 1 or t === 0 are the starting/ending points of the curve, so no * division is needed. * - * @returns {CubicBezier[]} Returns an array containing two bezier curves + * @returns {Array.} Returns an array containing two bezier curves * to the left and the right of t. */ CubicBezier.prototype.divideAtT = function (t) {