Last active
October 7, 2023 13:51
-
-
Save chestozo/0d28facf5a28509d4210 to your computer and use it in GitHub Desktop.
react.js mixin for inertial scroll (momentum scroll) on touch devices
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
On touch devices we have an intertial scroll effect (also called momentum scroll) | |
that is hard to detect (with scrollTop property for example). | |
See this article for problem details http://andyshora.com/mobile-scroll-event-problems.html | |
Still it is possible to detect inertial scroll by calculating velocity | |
with which user is scrolling element. | |
In this gist we use velocity formula from this article: | |
http://ariya.ofilabs.com/2013/11/javascript-kinetic-scrolling-part-2.html | |
With this information in our hands we can now detect scroll end moment. | |
This can be used to avoid heavy operations been done during page scroll. | |
For example if you start inertial scrolling and then go to save something in a WebSQL database | |
you may note a twitch effect. | |
To avoid this you can wait until page scroll is fully done and query your database after it | |
*/ | |
var TouchScrollMixin = { | |
handleTouchStart: function(evt) { | |
// Do not react to multitouch gestures. | |
if (evt.touches.length !== 1) { | |
return; | |
} | |
this._touching = true; | |
this._touchStartTime = new Date(); | |
this._touchScreenY = evt.touches[0].screenY; | |
}, | |
handleTouchEnd: function(evt) { | |
if (!this._touching) { | |
return; | |
} | |
this._touching = false; | |
this._touchEndTime = new Date(); | |
// Velocity calculate. | |
// http://ariya.ofilabs.com/2013/11/javascript-kinetic-scrolling-part-2.html . | |
var elapsedTime = (this._touchEndTime - this._touchStartTime); | |
var deltaY = Math.abs(evt.changedTouches[0].screenY - this._touchScreenY); | |
var velocity = 0.8 * (1000 * deltaY / (1 + elapsedTime)); | |
// Slow page scroll (no inertial effect). | |
// | |
// "touchstart" | |
// "scroll", 123 (ms from previous event) | |
// "scroll", 30 | |
// "scroll", 59 | |
// "scroll", 51 | |
// "scroll", 33 | |
// "scroll", 40 | |
// "touchend", 220 (scroll ends here) | |
// | |
// Big scrolling time means that scrolling was done slooooooowly. | |
// So that probably we are not going to have intertial effect. | |
// 170ms is an experimental value. | |
// FIXME this check is not solid still... | |
if (((deltaY / velocity) > 0.3) && elapsedTime > 170) { | |
this._asyncSave(); | |
return; | |
} | |
// Scroll page (inertial scroll) + stop it with tap. | |
// | |
// "touchstart" | |
// "scroll", 118 | |
// "scroll", 35 | |
// "touchend", 6 (ignore because we are going to have a big velocity here) | |
// "touchstart", 321 | |
// "scroll", 37 | |
// "touchend", 101 (scroll ends here - detect by slow velocity when user taps the screen) | |
// | |
if (velocity < 50) { | |
this._asyncSave(); | |
return; | |
} | |
}, | |
handleInertialScroll: function() { | |
if (this._touching) { | |
return; | |
} | |
// Pure inertial scroll, user waits for page to stop. | |
// "touchstart" | |
// "scroll", 248 | |
// "scroll", 65 | |
// "touchend", 13 | |
// "scroll", 1802 (scroll ends here because it is more than 700ms after touchend event) | |
// ===OR=== | |
// "touchstart" | |
// "scroll", 103 | |
// "scroll", 44 | |
// "touchend", 33 | |
// "scroll", 55 (do nothing here because it is only 55ms after touchend event) | |
// "scroll", 1797 (scroll ends here because it is more than 700ms after touchend event) | |
// | |
if ((new Date() - this._touchEndTime) > 700) { | |
this._asyncSave(); | |
} | |
} | |
}; | |
// You can use this mixin on components that are interested in scroll end event. | |
var ScrolledElement = React.createClass({ | |
mixins: [ TouchScrollMixin ], | |
_asyncSave: function() { ... }, | |
render: function() { | |
return ( | |
<div | |
onTouchStart={ this.handleTouchStart } | |
onTouchEnd={ this.handleTouchEnd } | |
onScroll={ this.handleInertialScroll } | |
/> | |
); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment