| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| /* eslint-disable */ |
| |
| |
| import './motion' |
| |
| var doc = window.document |
| var ua = window.navigator.userAgent |
| var scrollObjs = {} |
| var plugins = {} |
| var dpr = window.dpr |
| || (!!window.navigator.userAgent.match(/iPhone|iPad|iPod/) |
| ? document.documentElement.clientWidth / window.screen.availWidth |
| : 1) |
| var inertiaCoefficient = { |
| normal: [2 * dpr, 0.0015 * dpr], |
| slow: [1.5 * dpr, 0.003 * dpr], |
| veryslow: [1.5 * dpr, 0.005 * dpr] |
| } |
| var timeFunction = { |
| ease: [.25,.1,.25,1], |
| liner: [0,0,1,1], |
| 'ease-in': [.42,0,1,1], |
| 'ease-out': [0,0,.58,1], |
| 'ease-in-out': [.42,0,.58,1] |
| } |
| var Firefox = !!ua.match(/Firefox/i) |
| var IEMobile = !!ua.match(/IEMobile/i) |
| var cssPrefix = Firefox ? '-moz-' : IEMobile ? '-ms-' : '-webkit-' |
| var stylePrefix = Firefox ? 'Moz' : IEMobile ? 'ms' : 'webkit' |
| |
| function debugLog() { |
| if (lib.scroll.outputDebugLog) { |
| console.log.apply(console, arguments) |
| } |
| } |
| |
| function getBoundingClientRect(el) { |
| var rect = el.getBoundingClientRect() |
| if (!rect) { |
| rect = {} |
| rect.width = el.offsetWidth |
| rect.height = el.offsetHeight |
| |
| rect.left = el.offsetLeft |
| rect.top = el.offsetTop |
| var parent = el.offsetParent |
| while (parent) { |
| rect.left += parent.offsetLeft |
| rect.top += parent.offsetTop |
| parent = parent.offsetParent |
| } |
| |
| rect.right = rect.left + rect.width |
| rect.bottom = rect.top + rect.height |
| } |
| return rect |
| } |
| |
| function getMinScrollOffset(scrollObj) { |
| return 0 - scrollObj.options[scrollObj.axis + 'PaddingTop'] |
| } |
| |
| function getMaxScrollOffset(scrollObj) { |
| var rect = getBoundingClientRect(scrollObj.element) |
| var pRect = getBoundingClientRect(scrollObj.viewport) |
| var min = getMinScrollOffset(scrollObj) |
| if (scrollObj.axis === 'y') { |
| var max = 0 - rect.height + pRect.height |
| } else { |
| var max = 0 - rect.width + pRect.width |
| } |
| return Math.min( |
| max + scrollObj.options[scrollObj.axis + 'PaddingBottom'], |
| min |
| ) |
| } |
| |
| function getBoundaryOffset(scrollObj, offset) { |
| if (offset > scrollObj.minScrollOffset) { |
| return offset - scrollObj.minScrollOffset |
| } |
| if (offset < scrollObj.maxScrollOffset) { |
| return offset - scrollObj.maxScrollOffset |
| } |
| } |
| |
| function touchBoundary(scrollObj, offset) { |
| if (offset > scrollObj.minScrollOffset) { |
| offset = scrollObj.minScrollOffset |
| } else if (offset < scrollObj.maxScrollOffset) { |
| offset = scrollObj.maxScrollOffset |
| } |
| return offset |
| } |
| |
| function fireEvent(scrollObj, eventName, extra) { |
| debugLog(scrollObj.element.scrollId, eventName, extra) |
| var event = doc.createEvent('HTMLEvents') |
| event.initEvent(eventName, false, true) |
| event.scrollObj = scrollObj |
| if (extra) { |
| for (var key in extra) { |
| event[key] = extra[key] |
| } |
| } |
| scrollObj.element.dispatchEvent(event) |
| scrollObj.viewport.dispatchEvent(event) |
| } |
| |
| function getTransformOffset(scrollObj) { |
| var offset = {x: 0, y: 0} |
| var transform = getComputedStyle(scrollObj.element) |
| [stylePrefix + 'Transform'] |
| var matched |
| var reg1 = new RegExp('^matrix3d' |
| + '\\((?:[-\\d.]+,\\s*){12}([-\\d.]+),' |
| + '\\s*([-\\d.]+)(?:,\\s*[-\\d.]+){2}\\)') |
| var reg2 = new RegExp('^matrix' |
| + '\\((?:[-\\d.]+,\\s*){4}([-\\d.]+),\\s*([-\\d.]+)\\)$') |
| if (transform !== 'none') { |
| if ((matched = transform.match(reg1) || |
| transform.match(reg2))) { |
| offset.x = parseFloat(matched[1]) || 0 |
| offset.y = parseFloat(matched[2]) || 0 |
| } |
| } |
| |
| return offset |
| } |
| |
| var CSSMatrix = IEMobile ? 'MSCSSMatrix' : 'WebKitCSSMatrix' |
| var has3d = !!Firefox |
| || CSSMatrix in window |
| && 'm11' in new window[CSSMatrix]() |
| function getTranslate(x, y) { |
| x = parseFloat(x) |
| y = parseFloat(y) |
| |
| if (x != 0) { |
| x += 'px' |
| } |
| |
| if (y != 0) { |
| y += 'px' |
| } |
| |
| if (has3d) { |
| return 'translate3d(' + x + ', ' + y + ', 0)' |
| } |
| return 'translate(' + x + ', ' + y + ')' |
| } |
| |
| function setTransitionStyle(scrollObj, duration, timingFunction) { |
| if (duration === '' && timingFunction === '') { |
| scrollObj.element.style[stylePrefix + 'Transition'] = '' |
| } else { |
| scrollObj.element.style[stylePrefix + 'Transition'] |
| = cssPrefix + 'transform ' + duration + ' ' + timingFunction + ' 0s' |
| } |
| } |
| |
| function setTransformStyle(scrollObj, offset) { |
| var x = 0 |
| var y = 0 |
| if (typeof offset === 'object') { |
| x = offset.x |
| y = offset.y |
| } else { |
| if (scrollObj.axis === 'y') { |
| y = offset |
| } else { |
| x = offset |
| } |
| } |
| scrollObj.element.style[stylePrefix + 'Transform'] = getTranslate(x, y) |
| } |
| |
| var panning = false |
| doc.addEventListener('touchmove', function (e) { |
| if (panning) { |
| e.preventDefault() |
| return false |
| } |
| return true |
| }, false) |
| |
| function Scroll(element, options) { |
| var that = this |
| |
| options = options || {} |
| options.noBounce = !!options.noBounce |
| options.padding = options.padding || {} |
| |
| if (options.isPrevent == null) { |
| options.isPrevent = true |
| } else { |
| options.isPrevent = !!options.isPrevent |
| } |
| |
| if (options.isFixScrollendClick == null) { |
| options.isFixScrollendClick = true |
| } else { |
| options.isFixScrollendClick = !!options.isFixScrollendClick |
| } |
| |
| if (options.padding) { |
| options.yPaddingTop = -options.padding.top || 0 |
| options.yPaddingBottom = -options.padding.bottom || 0 |
| options.xPaddingTop = -options.padding.left || 0 |
| options.xPaddingBottom = -options.padding.right || 0 |
| } else { |
| options.yPaddingTop = 0 |
| options.yPaddingBottom = 0 |
| options.xPaddingTop = 0 |
| options.xPaddingBottom = 0 |
| } |
| |
| options.direction = options.direction || 'y' |
| options.inertia = options.inertia || 'normal' |
| |
| this.options = options |
| that.axis = options.direction |
| this.element = element |
| this.viewport = element.parentNode |
| this.plugins = {} |
| |
| this.element.scrollId = setTimeout(function () { |
| scrollObjs[that.element.scrollId + ''] = that |
| }, 1) |
| |
| this.viewport.addEventListener('touchstart', touchstartHandler, false) |
| this.viewport.addEventListener('touchend', touchendHandler, false) |
| this.viewport.addEventListener('touchcancel', touchendHandler, false) |
| this.viewport.addEventListener('panstart', panstartHandler, false) |
| this.viewport.addEventListener('panmove', panHandler, false) |
| this.viewport.addEventListener('panend', panendHandler, false) |
| |
| if (options.isPrevent) { |
| this.viewport.addEventListener('touchstart', function (e) { |
| panning = true |
| }, false) |
| that.viewport.addEventListener('touchend', function (e) { |
| panning = false |
| }, false) |
| } |
| |
| // if (options.isPrevent) { |
| // var d = this.axis === 'y'?'vertical':'horizontal' |
| // this.viewport.addEventListener(d + 'panstart', function (e) { |
| // panning = true |
| // }, false) |
| // that.viewport.addEventListener('panend', function (e) { |
| // panning = false |
| // }, false) |
| // } |
| |
| if (options.isFixScrollendClick) { |
| var preventScrollendClick |
| var fixScrollendClickTimeoutId |
| |
| this.viewport.addEventListener('scrolling', function () { |
| preventScrollendClick = true |
| fixScrollendClickTimeoutId && clearTimeout(fixScrollendClickTimeoutId) |
| fixScrollendClickTimeoutId = setTimeout(function (e) { |
| preventScrollendClick = false |
| }, 400) |
| }, false) |
| |
| function preventScrollendClickHandler(e) { |
| if (preventScrollendClick || isScrolling) { |
| e.preventDefault() |
| e.stopPropagation() |
| return false |
| } |
| return true |
| } |
| |
| function fireNiceTapEventHandler(e) { |
| if (!preventScrollendClick && !isScrolling) { |
| setTimeout(function () { |
| var niceTapEvent = document.createEvent('HTMLEvents') |
| niceTapEvent.initEvent('niceclick', true, true) |
| e.target.dispatchEvent(niceTapEvent) |
| }, 300) |
| } |
| } |
| |
| this.viewport.addEventListener('click', preventScrollendClickHandler) |
| this.viewport.addEventListener('tap', fireNiceTapEventHandler) |
| } |
| |
| function setTransitionEndHandler(h, t) { |
| if (options.useFrameAnimation) { |
| return |
| } |
| transitionEndHandler = null |
| clearTimeout(transitionEndTimeoutId) |
| |
| transitionEndTimeoutId = setTimeout(function () { |
| if (transitionEndHandler) { |
| transitionEndHandler = null |
| lib.animation.requestFrame(h) |
| } |
| }, (t || 400)) |
| |
| transitionEndHandler = h |
| } |
| |
| if (options.useFrameAnimation) { |
| var scrollAnimation |
| |
| Object.defineProperty(this, 'animation', { |
| get: function () { |
| return scrollAnimation |
| } |
| }) |
| } else { |
| var transitionEndHandler |
| var transitionEndTimeoutId = 0 |
| |
| element.addEventListener( |
| Firefox |
| ? 'transitionend' |
| : (stylePrefix + 'TransitionEnd'), function (e) { |
| if (transitionEndHandler) { |
| var handler = transitionEndHandler |
| |
| transitionEndHandler = null |
| clearTimeout(transitionEndTimeoutId) |
| |
| lib.animation.requestFrame(function () { |
| handler(e) |
| }) |
| } |
| }, false) |
| } |
| |
| var panFixRatio |
| var isScrolling |
| var isFlickScrolling |
| var cancelScrollEnd |
| |
| Object.defineProperty(this, 'isScrolling', { |
| get: function () { |
| return !!isScrolling |
| } |
| }) |
| |
| function isEnabled(e) { |
| if (!that.enabled) { |
| return false |
| } |
| |
| if (typeof e.isVertical != 'undefined') { |
| if (that.axis === 'y' && e.isVertical |
| || that.axis === 'x' && !e.isVertical) { |
| // gesture in same direction, stop bubbling up |
| e.stopPropagation() |
| } else { |
| // gesture in different direction, bubbling up |
| // to the top, without any other process |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| function touchstartHandler(e) { |
| if (!isEnabled(e)) { |
| return |
| } |
| |
| if (isScrolling) { |
| scrollEnd() |
| } |
| |
| if (options.useFrameAnimation) { |
| scrollAnimation && scrollAnimation.stop() |
| scrollAnimation = null |
| } else { |
| var transform = getTransformOffset(that) |
| setTransformStyle(that, transform) |
| setTransitionStyle(that, '', '') |
| transitionEndHandler = null |
| clearTimeout(transitionEndTimeoutId) |
| } |
| } |
| |
| function touchendHandler(e) { |
| if (!isEnabled(e)) { |
| return |
| } |
| |
| var s0 = getTransformOffset(that)[that.axis] |
| var boundaryOffset = getBoundaryOffset(that, s0) |
| |
| if (boundaryOffset) { |
| // dragging out of boundray, bounce is needed |
| var s1 = touchBoundary(that, s0) |
| |
| if (options.useFrameAnimation) { |
| // frame |
| var _s = s1 - s0 |
| scrollAnimation = new lib.animation( |
| 400, |
| lib.cubicbezier.ease, |
| 0, |
| function (i1, i2) { |
| var offset = (s0 + _s * i2).toFixed(2) |
| setTransformStyle(that, offset) |
| fireEvent(that, 'scrolling') |
| }) |
| scrollAnimation.onend(scrollEnd) |
| scrollAnimation.play() |
| } else { |
| // css |
| var offset = s1.toFixed(0) |
| setTransitionEndHandler(scrollEnd, 400) |
| setTransitionStyle(that, '0.4s', 'ease') |
| setTransformStyle(that, offset) |
| |
| lib.animation.requestFrame(function doScroll() { |
| if (isScrolling && that.enabled) { |
| fireEvent(that, 'scrolling') |
| lib.animation.requestFrame(doScroll) |
| } |
| }) |
| } |
| |
| if (boundaryOffset > 0) { |
| fireEvent(that, that.axis === 'y' ? 'pulldownend' : 'pullrightend') |
| } else if (boundaryOffset < 0) { |
| fireEvent(that, that.axis === 'y' ? 'pullupend' : 'pullleftend') |
| } |
| } else if (isScrolling) { |
| // without exceeding the boundary, just end it |
| scrollEnd() |
| } |
| } |
| |
| var lastDisplacement |
| function panstartHandler(e) { |
| if (!isEnabled(e)) { |
| return |
| } |
| |
| that.transformOffset = getTransformOffset(that) |
| that.minScrollOffset = getMinScrollOffset(that) |
| that.maxScrollOffset = getMaxScrollOffset(that) |
| panFixRatio = 2.5 |
| cancelScrollEnd = true |
| isScrolling = true |
| isFlickScrolling = false |
| fireEvent(that, 'scrollstart') |
| |
| lastDisplacement = e['displacement' + that.axis.toUpperCase()] |
| } |
| |
| |
| function panHandler(e) { |
| if (!isEnabled(e)) { |
| return |
| } |
| |
| // finger move less than 5 px. just ignore that. |
| var displacement = e['displacement' + that.axis.toUpperCase()] |
| if (Math.abs(displacement - lastDisplacement) < 5) { |
| e.stopPropagation() |
| return |
| } |
| lastDisplacement = displacement |
| |
| var offset = that.transformOffset[that.axis] + displacement |
| if (offset > that.minScrollOffset) { |
| offset = that.minScrollOffset |
| + (offset - that.minScrollOffset) / panFixRatio |
| panFixRatio *= 1.003 |
| } else if (offset < that.maxScrollOffset) { |
| offset = that.maxScrollOffset |
| - (that.maxScrollOffset - offset) / panFixRatio |
| panFixRatio *= 1.003 |
| } |
| if (panFixRatio > 4) { |
| panFixRatio = 4 |
| } |
| |
| // tell whether or not reach the fringe |
| var boundaryOffset = getBoundaryOffset(that, offset) |
| if (boundaryOffset) { |
| fireEvent( |
| that, |
| boundaryOffset > 0 |
| ? (that.axis === 'y' ? 'pulldown' : 'pullright') |
| : (that.axis === 'y' ? 'pullup' : 'pullleft'), { |
| boundaryOffset: Math.abs(boundaryOffset) |
| }) |
| if (that.options.noBounce) { |
| offset = touchBoundary(that, offset) |
| } |
| } |
| |
| setTransformStyle(that, offset.toFixed(2)) |
| fireEvent(that, 'scrolling') |
| } |
| |
| function panendHandler(e) { |
| if (!isEnabled(e)) { |
| return |
| } |
| |
| if (e.isSwipe) { |
| flickHandler(e) |
| } |
| } |
| |
| function flickHandler(e) { |
| cancelScrollEnd = true |
| |
| var v0, a0, t0, s0, s, motion0 |
| var v1, a1, t1, s1, motion1,sign |
| var v2, a2, t2, s2, motion2, ft |
| |
| s0 = getTransformOffset(that)[that.axis] |
| var boundaryOffset0 = getBoundaryOffset(that, s0) |
| if (!boundaryOffset0) { |
| // when fingers left the range of screen, let touch end handler |
| // to deal with it. |
| // when fingers left the screen, but still in the range of |
| // screen, calculate the intertia. |
| v0 = e['velocity' + that.axis.toUpperCase()] |
| |
| var maxV = 2 |
| var friction = 0.0015 |
| if (options.inertia && inertiaCoefficient[options.inertia]) { |
| maxV = inertiaCoefficient[options.inertia][0] |
| friction = inertiaCoefficient[options.inertia][1] |
| } |
| |
| if (v0 > maxV) { |
| v0 = maxV |
| } |
| if (v0 < -maxV) { |
| v0 = -maxV |
| } |
| a0 = friction * (v0 / Math.abs(v0)) |
| motion0 = new lib.motion({ |
| v: v0, |
| a: -a0 |
| }) |
| t0 = motion0.t |
| s = s0 + motion0.s |
| |
| var boundaryOffset1 = getBoundaryOffset(that, s) |
| if (boundaryOffset1) { |
| debugLog('inertial calculation has exceeded the boundary', |
| boundaryOffset1) |
| |
| v1 = v0 |
| a1 = a0 |
| if (boundaryOffset1 > 0) { |
| s1 = that.minScrollOffset |
| sign = 1 |
| } else { |
| s1 = that.maxScrollOffset |
| sign = -1 |
| } |
| motion1 = new lib.motion({ |
| v: sign * v1, |
| a: -sign * a1, |
| s: Math.abs(s1 - s0) |
| }) |
| t1 = motion1.t |
| var timeFunction1 = motion1.generateCubicBezier() |
| |
| v2 = v1 - a1 * t1 |
| a2 = 0.03 * (v2 / Math.abs(v2)) |
| motion2 = new lib.motion({ |
| v: v2, |
| a: -a2 |
| }) |
| t2 = motion2.t |
| s2 = s1 + motion2.s |
| var timeFunction2 = motion2.generateCubicBezier() |
| |
| if (options.noBounce) { |
| debugLog('no bounce effect') |
| |
| if (s0 !== s1) { |
| if (options.useFrameAnimation) { |
| // frame |
| var _s = s1 - s0 |
| var bezier = lib.cubicbezier( |
| timeFunction1[0][0], |
| timeFunction1[0][1], |
| timeFunction1[1][0], |
| timeFunction1[1][1] |
| ) |
| scrollAnimation = new lib.animation( |
| t1.toFixed(0), |
| bezier, |
| 0, |
| function (i1, i2) { |
| var offset = (s0 + _s * i2) |
| getTransformOffset(that, offset.toFixed(2)) |
| fireEvent(that, 'scrolling', { |
| afterFlick: true |
| }) |
| }) |
| |
| scrollAnimation.onend(scrollEnd) |
| |
| scrollAnimation.play() |
| } else { |
| // css |
| var offset = s1.toFixed(0) |
| setTransitionEndHandler( |
| scrollEnd, |
| (t1 / 1000).toFixed(2) * 1000 |
| ) |
| setTransitionStyle( |
| that, |
| (t1 / 1000).toFixed(2) + 's', |
| 'cubic-bezier(' + timeFunction1 + ')' |
| ) |
| setTransformStyle(that, offset) |
| } |
| } else { |
| scrollEnd() |
| } |
| } else if (s0 !== s2) { |
| debugLog( |
| 'scroll for inertia', |
| 's=' + s2.toFixed(0), |
| 't=' + ((t1 + t2) / 1000).toFixed(2) |
| ) |
| |
| if (options.useFrameAnimation) { |
| var _s = s2 - s0 |
| var bezier = lib.cubicbezier.easeOut |
| scrollAnimation = new lib.animation( |
| (t1 + t2).toFixed(0), |
| bezier, |
| 0, |
| function (i1, i2) { |
| var offset = s0 + _s * i2 |
| setTransformStyle(that, offset.toFixed(2)) |
| fireEvent(that, 'scrolling',{ |
| afterFlick: true |
| }) |
| }) |
| |
| scrollAnimation.onend(function () { |
| if (!that.enabled) { |
| return |
| } |
| |
| var _s = s1 - s2 |
| var bezier = lib.cubicbezier.ease |
| scrollAnimation = new lib.animation( |
| 400, |
| bezier, |
| 0, |
| function (i1, i2) { |
| var offset = s2 + _s * i2 |
| setTransformStyle(that, offset.toFixed(2)) |
| fireEvent(that, 'scrolling',{ |
| afterFlick: true |
| }) |
| }) |
| |
| scrollAnimation.onend(scrollEnd) |
| |
| scrollAnimation.play() |
| }) |
| |
| scrollAnimation.play() |
| } else { |
| var offset = s2.toFixed(0) |
| setTransitionEndHandler(function (e) { |
| if (!that.enabled) { |
| return |
| } |
| |
| debugLog('inertial bounce', |
| 's=' + s1.toFixed(0), |
| 't=400' |
| ) |
| |
| if (s2 !== s1) { |
| var offset = s1.toFixed(0) |
| setTransitionStyle(that, '0.4s', 'ease') |
| setTransformStyle(that, offset) |
| setTransitionEndHandler(scrollEnd, 400) |
| } else { |
| scrollEnd() |
| } |
| }, ((t1 + t2) / 1000).toFixed(2) * 1000) |
| |
| setTransitionStyle( |
| that, |
| ((t1 + t2) / 1000).toFixed(2) + 's', |
| 'ease-out' |
| ) |
| setTransformStyle(that, offset) |
| } |
| } else { |
| scrollEnd() |
| } |
| } else { |
| debugLog('inertial calculation hasn\'t exceeded the boundary') |
| var timeFunction = motion0.generateCubicBezier() |
| |
| if (options.useFrameAnimation) { |
| // frame |
| var _s = s - s0 |
| var bezier = lib.cubicbezier( |
| timeFunction[0][0], |
| timeFunction[0][1], |
| timeFunction[1][0], |
| timeFunction[1][1] |
| ) |
| scrollAnimation = new lib.animation( |
| t0.toFixed(0), |
| bezier, |
| 0, |
| function (i1, i2) { |
| var offset = (s0 + _s * i2).toFixed(2) |
| setTransformStyle(that, offset) |
| fireEvent(that, 'scrolling',{ |
| afterFlick: true |
| }) |
| }) |
| |
| scrollAnimation.onend(scrollEnd) |
| |
| scrollAnimation.play() |
| } else { |
| // css |
| var offset = s.toFixed(0) |
| setTransitionEndHandler(scrollEnd, (t0 / 1000).toFixed(2) * 1000) |
| setTransitionStyle( |
| that, |
| (t0 / 1000).toFixed(2) + 's', |
| 'cubic-bezier(' + timeFunction + ')' |
| ) |
| setTransformStyle(that, offset) |
| } |
| } |
| |
| |
| isFlickScrolling = true |
| if (!options.useFrameAnimation) { |
| lib.animation.requestFrame(function doScroll() { |
| if (isScrolling && isFlickScrolling && that.enabled) { |
| fireEvent(that, 'scrolling', { |
| afterFlick: true |
| }) |
| lib.animation.requestFrame(doScroll) |
| } |
| }) |
| } |
| } |
| } |
| |
| function scrollEnd() { |
| if (!that.enabled) { |
| return |
| } |
| |
| cancelScrollEnd = false |
| |
| setTimeout(function () { |
| if (!cancelScrollEnd && isScrolling) { |
| isScrolling = false |
| isFlickScrolling = false |
| |
| if (options.useFrameAnimation) { |
| scrollAnimation && scrollAnimation.stop() |
| scrollAnimation = null |
| } else { |
| setTransitionStyle(that, '', '') |
| } |
| fireEvent(that, 'scrollend') |
| } |
| }, 50) |
| } |
| |
| var proto = { |
| init: function () { |
| this.enable() |
| this.refresh() |
| this.scrollTo(0) |
| return this |
| }, |
| |
| enable: function () { |
| this.enabled = true |
| return this |
| }, |
| |
| disable: function () { |
| var el = this.element |
| this.enabled = false |
| |
| if (this.options.useFrameAnimation) { |
| scrollAnimation && scrollAnimation.stop() |
| } else { |
| lib.animation.requestFrame(function () { |
| el.style[stylePrefix + 'Transform'] |
| = getComputedStyle(el)[stylePrefix + 'Transform'] |
| }) |
| } |
| |
| return this |
| }, |
| |
| getScrollWidth: function () { |
| return getBoundingClientRect(this.element).width |
| }, |
| |
| getScrollHeight: function () { |
| return getBoundingClientRect(this.element).height |
| }, |
| |
| getScrollLeft: function () { |
| return -getTransformOffset(this).x - this.options.xPaddingTop |
| }, |
| |
| getScrollTop: function () { |
| return -getTransformOffset(this).y - this.options.yPaddingTop |
| }, |
| |
| getMaxScrollLeft: function () { |
| return -that.maxScrollOffset - this.options.xPaddingTop |
| }, |
| |
| getMaxScrollTop: function () { |
| return -that.maxScrollOffset - this.options.yPaddingTop |
| }, |
| |
| getBoundaryOffset: function () { |
| return Math.abs( |
| getBoundaryOffset(this, getTransformOffset(this)[this.axis]) || 0 |
| ) |
| }, |
| |
| refresh: function () { |
| var el = this.element |
| var isVertical = (this.axis === 'y') |
| var type = isVertical ? 'height' : 'width' |
| var size, rect, extraSize |
| |
| function getExtraSize(el, isVertical) { |
| var extraType = isVertical ? ['top', 'bottom'] : ['left', 'right'] |
| return parseFloat( |
| getComputedStyle(el.firstElementChild)['margin-' + extraType[0]] |
| ) + parseFloat( |
| getComputedStyle(el.lastElementChild)['margin-' + extraType[1]] |
| ) |
| } |
| |
| if (this.options[type] != null) { |
| // use options |
| size = this.options[type] |
| } else if (el.childElementCount <= 0) { |
| el.style[type] = 'auto' |
| size = null |
| } else if (!!this.options.useElementRect) { |
| el.style[type] = 'auto' |
| rect = getBoundingClientRect(el) |
| size = rect[type] |
| size += getExtraSize(el, isVertical) |
| } else { |
| var range, rect |
| var firstEl = el.firstElementChild |
| var lastEl = el.lastElementChild |
| |
| if (document.createRange && !this.options.ignoreOverflow) { |
| // use range |
| range = document.createRange() |
| range.selectNodeContents(el) |
| rect = getBoundingClientRect(range) |
| } |
| |
| if (rect) { |
| size = rect[type] |
| } else { |
| // use child offsets |
| while (firstEl) { |
| if (getBoundingClientRect(firstEl)[type] === 0 |
| && firstEl.nextElementSibling) { |
| firstEl = firstEl.nextElementSibling |
| } else { |
| break |
| } |
| } |
| |
| while (lastEl && lastEl !== firstEl) { |
| if (getBoundingClientRect(lastEl)[type] === 0 |
| && lastEl.previousElementSibling) { |
| lastEl = lastEl.previousElementSibling |
| } else { |
| break |
| } |
| } |
| |
| size = getBoundingClientRect(lastEl)[ |
| isVertical ? 'bottom' : 'right'] |
| - getBoundingClientRect(firstEl)[ |
| isVertical ? 'top' : 'left'] |
| } |
| |
| size += getExtraSize(el, isVertical) |
| |
| } |
| |
| el.style[type] = size ? size + 'px' : 'auto' |
| |
| this.transformOffset = getTransformOffset(this) |
| this.minScrollOffset = getMinScrollOffset(this) |
| this.maxScrollOffset = getMaxScrollOffset(this) |
| |
| this.scrollTo( |
| -this.transformOffset[this.axis] |
| - this.options[this.axis + 'PaddingTop'] |
| ) |
| fireEvent(this, 'contentrefresh') |
| |
| return this |
| }, |
| |
| offset: function (childEl) { |
| var elRect = getBoundingClientRect(this.element) |
| var childRect = getBoundingClientRect(childEl) |
| if (this.axis === 'y') { |
| var offsetRect = { |
| top: childRect.top - elRect.top - this.options.yPaddingTop, |
| left: childRect.left - elRect.left, |
| right: elRect.right - childRect.right, |
| width: childRect.width, |
| height: childRect.height |
| } |
| |
| offsetRect.bottom = offsetRect.top + offsetRect.height |
| } else { |
| var offsetRect = { |
| top: childRect.top - elRect.top, |
| bottom: elRect.bottom - childRect.bottom, |
| left: childRect.left - elRect.left - this.options.xPaddingTop, |
| width: childRect.width, |
| height: childRect.height |
| } |
| |
| offsetRect.right = offsetRect.left + offsetRect.width |
| } |
| return offsetRect |
| }, |
| |
| getRect: function (childEl) { |
| var viewRect = getBoundingClientRect(this.viewport) |
| var childRect = getBoundingClientRect(childEl) |
| if (this.axis === 'y') { |
| var offsetRect = { |
| top: childRect.top - viewRect.top, |
| left: childRect.left - viewRect.left, |
| right: viewRect.right - childRect.right, |
| width: childRect.width, |
| height: childRect.height |
| } |
| |
| offsetRect.bottom = offsetRect.top + offsetRect.height |
| } else { |
| var offsetRect = { |
| top: childRect.top - viewRect.top, |
| bottom: viewRect.bottom - childRect.bottom, |
| left: childRect.left - viewRect.left, |
| width: childRect.width, |
| height: childRect.height |
| } |
| |
| offsetRect.right = offsetRect.left + offsetRect.width |
| } |
| return offsetRect |
| }, |
| |
| isInView: function (childEl) { |
| var viewRect = this.getRect(this.viewport) |
| var childRect = this.getRect(childEl) |
| if (this.axis === 'y') { |
| return viewRect.top < childRect.bottom |
| && viewRect.bottom > childRect.top |
| } |
| return viewRect.left < childRect.right |
| && viewRect.right > childRect.left |
| }, |
| |
| scrollTo: function (offset, isSmooth) { |
| var that = this |
| var element = this.element |
| |
| offset = -offset - this.options[this.axis + 'PaddingTop'] |
| offset = touchBoundary(this, offset) |
| |
| isScrolling = true |
| if (isSmooth === true) { |
| if (this.options.useFrameAnimation) { |
| var s0 = getTransformOffset(that)[this.axis] |
| var _s = offset - s0 |
| scrollAnimation = new lib.animation( |
| 400, |
| lib.cubicbezier.easeInOut, |
| 0, |
| function (i1, i2) { |
| var offset = (s0 + _s * i2).toFixed(2) |
| setTransformStyle(that, offset) |
| fireEvent(that, 'scrolling') |
| }) |
| |
| scrollAnimation.onend(scrollEnd) |
| |
| scrollAnimation.play() |
| } else { |
| setTransitionEndHandler(scrollEnd, 400) |
| setTransitionStyle(that, '0.4s', 'ease-in-out') |
| setTransformStyle(that, offset) |
| |
| function _cancelScroll() { |
| if (isScrolling && that.enabled) { |
| fireEvent(that, 'scrolling') |
| lib.animation.requestFrame(_cancelScroll) |
| } |
| } |
| |
| lib.animation.requestFrame(_cancelScroll) |
| } |
| } else { |
| if (!this.options.useFrameAnimation) { |
| setTransitionStyle(that, '', '') |
| } |
| setTransformStyle(that, offset) |
| scrollEnd() |
| } |
| |
| return this |
| }, |
| |
| scrollToElement: function (childEl, isSmooth, topOffset) { |
| var offset = this.offset(childEl) |
| offset = offset[this.axis === 'y'?'top':'left'] |
| topOffset && (offset += topOffset) |
| return this.scrollTo(offset, isSmooth) |
| }, |
| |
| getViewWidth: function () { |
| return getBoundingClientRect(this.viewport).width |
| }, |
| |
| getViewHeight: function () { |
| return getBoundingClientRect(this.viewport).height |
| }, |
| |
| addPulldownHandler: function (handler) { |
| var that = this |
| this.element.addEventListener('pulldownend', function (e) { |
| that.disable() |
| handler.call(that, e, function () { |
| that.scrollTo(0, true) |
| that.refresh() |
| that.enable() |
| }) |
| }, false) |
| |
| return this |
| }, |
| |
| addPullupHandler: function (handler) { |
| var that = this |
| |
| this.element.addEventListener('pullupend', function (e) { |
| that.disable() |
| handler.call(that, e, function () { |
| that.scrollTo(that.getScrollHeight(), true) |
| that.refresh() |
| that.enable() |
| }) |
| }, false) |
| |
| return this |
| }, |
| |
| addScrollstartHandler: function (handler) { |
| var that = this |
| this.element.addEventListener('scrollstart', function (e) { |
| handler.call(that, e) |
| }, false) |
| |
| return this |
| }, |
| |
| addScrollingHandler: function (handler) { |
| var that = this |
| this.element.addEventListener('scrolling', function (e) { |
| handler.call(that, e) |
| }, false) |
| |
| return this |
| }, |
| |
| addScrollendHandler: function (handler) { |
| var that = this |
| this.element.addEventListener('scrollend', function (e) { |
| handler.call(that, e) |
| }, false) |
| |
| return this |
| }, |
| |
| addContentrenfreshHandler: function (handler) { |
| var that = this |
| this.element.addEventListener('contentrefresh', function (e) { |
| handler.call(that, e) |
| }, false) |
| }, |
| |
| addEventListener: function (name, handler, useCapture) { |
| var that = this |
| this.element.addEventListener(name, function (e) { |
| handler.call(that, e) |
| }, !!useCapture) |
| }, |
| |
| removeEventListener: function (name, handler) { |
| var that = this |
| this.element.removeEventListener(name, function (e) { |
| handler.call(that, e) |
| }) |
| }, |
| |
| enablePlugin: function (name, options) { |
| var plugin = plugins[name] |
| if (plugin && !this.plugins[name]) { |
| this.plugins[name] = true |
| options = options || {} |
| plugin.call(this, name, options) |
| } |
| return this |
| } |
| } |
| |
| for (var k in proto) { |
| this[k] = proto[k] |
| } |
| // delete proto |
| } |
| |
| lib.scroll = function (el, options) { |
| if (arguments.length === 1 && !(arguments[0] instanceof HTMLElement)) { |
| options = arguments[0] |
| if (options.scrollElement) { |
| el = options.scrollElement |
| } else if (options.scrollWrap) { |
| el = options.scrollWrap.firstElementChild |
| } else { |
| throw new Error('no scroll element') |
| } |
| } |
| |
| if (!el.parentNode) { |
| throw new Error('wrong dom tree') |
| } |
| if (options |
| && options.direction |
| && ['x', 'y'].indexOf(options.direction) < 0) { |
| throw new Error('wrong direction') |
| } |
| |
| var scroll |
| if (options.downgrade === true |
| && lib.scroll.downgrade) { |
| scroll = lib.scroll.downgrade(el, options) |
| } else { |
| if (el.scrollId) { |
| scroll = scrollObjs[el.scrollId] |
| } else { |
| scroll = new Scroll(el, options) |
| } |
| } |
| return scroll |
| } |
| |
| lib.scroll.plugin = function (name, constructor) { |
| if (constructor) { |
| name = name.split(',') |
| name.forEach(function (n) { |
| plugins[n] = constructor |
| }) |
| } else { |
| return plugins[name] |
| } |
| } |