Skip to content

Instantly share code, notes, and snippets.

@kontur
Created July 27, 2017 07:44
Show Gist options
  • Save kontur/d35cbd91a1f525f9fe44067e6b7cc36d to your computer and use it in GitHub Desktop.
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
/**
* 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