From 3aa60cc11b885fb6ebb19b21ab23325ae6664d3a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 23 Jun 2017 18:34:07 +0800 Subject: [PATCH] ScrollView scrolls with animation --- Libraries/ListView/ScrollResponder.web.js | 10 ++-- Libraries/ScrollView/ScrollView.web.js | 20 +++----- Libraries/Utilties/animatedScrollTo.js | 57 +++++++++++++++++++++++ 3 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 Libraries/Utilties/animatedScrollTo.js diff --git a/Libraries/ListView/ScrollResponder.web.js b/Libraries/ListView/ScrollResponder.web.js index 04eb969..2487fe9 100644 --- a/Libraries/ListView/ScrollResponder.web.js +++ b/Libraries/ListView/ScrollResponder.web.js @@ -13,6 +13,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import warning from 'fbjs/lib/warning'; +import animatedScrollTo from '../Utilties/animatedScrollTo'; /** * Mixin that can be integrated in order to handle scrolling that plays well @@ -93,6 +94,7 @@ import warning from 'fbjs/lib/warning'; */ const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16; +const ANIMATION_DURATION_MS = 300; type State = { isTouching: boolean; @@ -351,7 +353,9 @@ let ScrollResponderMixin = { */ scrollResponderScrollTo: function(offsetX: number, offsetY: number) { // TODO: Add scroll animation - this.scrollResponderScrollWithouthAnimationTo(offsetX, offsetY); + let node = ReactDOM.findDOMNode(this); + + animatedScrollTo(node, {y: offsetY, x: offsetX}, ANIMATION_DURATION_MS); }, /** @@ -361,8 +365,8 @@ let ScrollResponderMixin = { scrollResponderScrollWithouthAnimationTo: function(offsetX: number, offsetY: number) { let node = ReactDOM.findDOMNode(this); - node.offsetX = offsetX; - node.offsetY = offsetY; + node.scrollLeft = offsetX; + node.scrollTop = offsetY; }, /** diff --git a/Libraries/ScrollView/ScrollView.web.js b/Libraries/ScrollView/ScrollView.web.js index 53dbfd3..23c1f39 100644 --- a/Libraries/ScrollView/ScrollView.web.js +++ b/Libraries/ScrollView/ScrollView.web.js @@ -55,26 +55,18 @@ class ScrollView extends Component { return this.refs[INNERVIEW]; } - scrollTo(opts) { + scrollTo(opts: { x?: number, y?: number, animated?: boolean }) { // $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now // this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0); if (typeof opts === 'number') { opts = { y: opts, x: arguments[1] }; } - this.scrollWithoutAnimationTo(opts.y, opts.x); - } - - scrollWithoutAnimationTo(destY?: number, destX?: number) { - // $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now - // this.getScrollResponder().scrollResponderScrollWithouthAnimationTo( - // destX || 0, - // destY || 0, - // ); - - this._scrollViewDom = ReactDOM.findDOMNode(this.refs[SCROLLVIEW]); - this._scrollViewDom.scrollTop = destY || 0; - this._scrollViewDom.scrollLeft = destX || 0; + if (opts.animated) { + this.getScrollResponder().scrollResponderScrollTo(opts.x || 0, opts.y || 0); + } else { + this.getScrollResponder().scrollResponderScrollWithouthAnimationTo(opts.x || 0, opts.y || 0); + } } handleScroll(e: Event) { diff --git a/Libraries/Utilties/animatedScrollTo.js b/Libraries/Utilties/animatedScrollTo.js new file mode 100644 index 0000000..a42f634 --- /dev/null +++ b/Libraries/Utilties/animatedScrollTo.js @@ -0,0 +1,57 @@ +// Modified from https://github.com/madebysource/animated-scrollto/blob/master/animatedScrollTo.js + +(function (window) { + var requestAnimFrame = (function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(callback){window.setTimeout(callback,1000/60);};})(); + + var easeInOutQuad = function (t, b, c, d) { + t /= d/2; + if (t < 1) return c/2*t*t + b; + t--; + return -c/2 * (t*(t-2) - 1) + b; + }; + + var animatedScrollTo = function (element, to, duration, callback) { + var startY = element.scrollTop; + var startX = element.scrollLeft; + var changeY = to.y - startY; + var changeX = to.x - startX; + var animationStart = +new Date(); + var animating = true; + var lastposX = null; + var lastposY = null; + + var animateScroll = function() { + if (!animating) { + return; + } + requestAnimFrame(animateScroll); + var now = +new Date(); + var valY = Math.floor(easeInOutQuad(now - animationStart, startY, changeY, duration)); + var valX = Math.floor(easeInOutQuad(now - animationStart, startX, changeX, duration)); + + // If last position is not element's scroll position, maybe user was scrolled. + if (lastposY === null || + (lastposY === element.scrollTop && lastposX === element.scrollLeft)) { + lastposY = valY; + lastposX = valX; + element.scrollTop = valY; + element.scrollLeft = valX; + } else { + animating = false; + } + if (now > animationStart + duration) { + element.scrollTop = to.y; + element.scrollLeft = to.x; + animating = false; + if (callback) { callback(); } + } + }; + requestAnimFrame(animateScroll); + }; + + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = animatedScrollTo; + } else { + window.animatedScrollTo = animatedScrollTo; + } +})(window);