Created
July 27, 2017 07:44
-
-
Save kontur/d35cbd91a1f525f9fe44067e6b7cc36d to your computer and use it in GitHub Desktop.
Scroller - takes element, scroll() calls, and continues native-ish scrolling after last scroll() call
This file contains 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
/** | |
* Helper class that simulates some-of-what nativish | |
* scrolling that continues after dragging (explicit calls to scroll()) | |
* have stopped | |
*/ | |
// libraries | |
import $ from "./Zepto" | |
export default class Scroller { | |
constructor($element) { | |
this.$element = $element | |
// the current scrolling speed (while animating, or average of last movement) | |
this.speedY = 0 | |
// where the scrollable element is scrolled at currently | |
this.lastY = 0 | |
// minimum distance to scroll, beyond lower we don't animate | |
this.threshold = 0.01 | |
// the animation easing; could also use easing function, now just | |
// exponentially multiply by this factor | |
this.easing = 0.95 | |
// when storing the last moved distance, always use part of this.speedY | |
// and the other part the current last distance, to somewhat ease | |
// how an abrupt acceleration or deacceleration just before releasing | |
// to animating should affect the movement | |
this.decay = 0.5 | |
// store ref to requestAnimationFrame for cancelling | |
this.animation = null | |
// animation flag; new call to scroll() cancels automatic animation | |
this.animate = false | |
} | |
/** | |
* Move to y | |
*/ | |
scroll(y) { | |
// store how much we moved since last recorded y | |
let lastSpeed = this.lastY - y | |
// perform the actual scroll | |
this.$element.scrollTop(y) | |
// cancel perpetual animation frame animation | |
this.animate = false | |
// on each manual scroll update the speed (distance) at which this manual | |
// scroll happened, so that when this was the last manual scroll | |
// we can continue and ease out this scroll | |
// always balance this current speed with what was last recorded | |
this.speedY = this.speedY * this.decay + lastSpeed * (1 - this.decay) | |
// record current position | |
this.lastY = y | |
// trigger perpetual animation; only a new call to scroll will | |
// cancel it, or having finished animating | |
this.animation = window.requestAnimationFrame(this.continueScroll.bind(this)) | |
} | |
/** | |
* Perpetual animation callback for scrolling after the last scroll() call | |
*/ | |
continueScroll() { | |
// check if we should still animate or if the animation got cancelled | |
if (!this.animate) { | |
window.cancelAnimationFrame(this.animation) | |
} | |
if (Math.abs(this.speedY) > this.threshold) { | |
// scroll by speed, ease out current speed | |
this.speedY *= this.easing | |
this.lastY -= this.speedY | |
// stop animating if we reached the end of the scrollable element | |
if (this.lastY <= 0) { | |
this.animate = false | |
} | |
// perform the actual scroll | |
this.$element.scrollTop(this.lastY) | |
window.requestAnimationFrame(this.continueScroll.bind(this)) | |
} else { | |
// below threshold, stop animation by not issueing a new frame | |
// also store status for reference | |
this.animate = false | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment