Created
July 15, 2022 13:44
-
-
Save nitori/d5a2b11c867d66eff99736ca8d573c6f to your computer and use it in GitHub Desktop.
JavaScript simple smooth scroll
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
// Sourced from: https://easings.net/ | |
/** | |
* @enum {Easings} | |
*/ | |
export const Easings = { | |
easeInSine: 'easeInSine', | |
easeOutSine: 'easeOutSine', | |
easeInOutSine: 'easeInOutSine', | |
easeInQuad: 'easeInQuad', | |
easeOutQuad: 'easeOutQuad', | |
easeInOutQuad: 'easeInOutQuad', | |
easeInCubic: 'easeInCubic', | |
easeOutCubic: 'easeOutCubic', | |
easeInOutCubic: 'easeInOutCubic', | |
easeInQuart: 'easeInQuart', | |
easeOutQuart: 'easeOutQuart', | |
easeInOutQuart: 'easeInOutQuart', | |
easeInQuint: 'easeInQuint', | |
easeOutQuint: 'easeOutQuint', | |
easeInOutQuint: 'easeInOutQuint', | |
easeInExpo: 'easeInExpo', | |
easeOutExpo: 'easeOutExpo', | |
easeInOutExpo: 'easeInOutExpo', | |
easeInCirc: 'easeInCirc', | |
easeOutCirc: 'easeOutCirc', | |
easeInOutCirc: 'easeInOutCirc', | |
easeInBack: 'easeInBack', | |
easeOutBack: 'easeOutBack', | |
easeInOutBack: 'easeInOutBack', | |
easeInElastic: 'easeInElastic', | |
easeOutElastic: 'easeOutElastic', | |
easeInOutElastic: 'easeInOutElastic', | |
easeInBounce: 'easeInBounce', | |
easeOutBounce: 'easeOutBounce', | |
easeInOutBounce: 'easeInOutBounce', | |
} | |
const easings = { | |
easeInSine: function (x) { | |
return 1 - Math.cos((x * Math.PI) / 2); | |
}, | |
easeOutSine: function (x) { | |
return Math.sin((x * Math.PI) / 2); | |
}, | |
easeInOutSine: function (x) { | |
return -(Math.cos(Math.PI * x) - 1) / 2; | |
}, | |
easeInQuad: function (x) { | |
return x * x; | |
}, | |
easeOutQuad: function (x) { | |
return 1 - (1 - x) * (1 - x); | |
}, | |
easeInOutQuad: function (x) { | |
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; | |
}, | |
easeInCubic: function (x) { | |
return x * x * x; | |
}, | |
easeOutCubic: function (x) { | |
return 1 - Math.pow(1 - x, 3); | |
}, | |
easeInOutCubic: function (x) { | |
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; | |
}, | |
easeInQuart: function (x) { | |
return x * x * x * x; | |
}, | |
easeOutQuart: function (x) { | |
return 1 - Math.pow(1 - x, 4); | |
}, | |
easeInOutQuart: function (x) { | |
return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; | |
}, | |
easeInQuint: function (x) { | |
return x * x * x * x * x; | |
}, | |
easeOutQuint: function (x) { | |
return 1 - Math.pow(1 - x, 5); | |
}, | |
easeInOutQuint: function (x) { | |
return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; | |
}, | |
easeInExpo: function (x) { | |
return x === 0 ? 0 : Math.pow(2, 10 * x - 10); | |
}, | |
easeOutExpo: function (x) { | |
return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); | |
}, | |
easeInOutExpo: function (x) { | |
return x === 0 | |
? 0 | |
: x === 1 | |
? 1 | |
: x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 | |
: (2 - Math.pow(2, -20 * x + 10)) / 2; | |
}, | |
easeInCirc: function (x) { | |
return 1 - Math.sqrt(1 - Math.pow(x, 2)); | |
}, | |
easeOutCirc: function (x) { | |
return Math.sqrt(1 - Math.pow(x - 1, 2)); | |
}, | |
easeInOutCirc: function (x) { | |
return x < 0.5 | |
? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 | |
: (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; | |
}, | |
easeInBack: function (x) { | |
const c1 = 1.70158; | |
const c3 = c1 + 1; | |
return c3 * x * x * x - c1 * x * x; | |
}, | |
easeOutBack: function (x) { | |
const c1 = 1.70158; | |
const c3 = c1 + 1; | |
return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); | |
}, | |
easeInOutBack: function (x) { | |
const c1 = 1.70158; | |
const c2 = c1 * 1.525; | |
return x < 0.5 | |
? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 | |
: (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; | |
}, | |
easeInElastic: function (x) { | |
const c4 = (2 * Math.PI) / 3; | |
return x === 0 | |
? 0 | |
: x === 1 | |
? 1 | |
: -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); | |
}, | |
easeOutElastic: function (x) { | |
const c4 = (2 * Math.PI) / 3; | |
return x === 0 | |
? 0 | |
: x === 1 | |
? 1 | |
: Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; | |
}, | |
easeInOutElastic: function (x) { | |
const c5 = (2 * Math.PI) / 4.5; | |
return x === 0 | |
? 0 | |
: x === 1 | |
? 1 | |
: x < 0.5 | |
? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 | |
: (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; | |
}, | |
easeInBounce: function (x) { | |
return 1 - easings.easeOutBounce(1 - x); | |
}, | |
easeOutBounce: function (x) { | |
const n1 = 7.5625; | |
const d1 = 2.75; | |
if (x < 1 / d1) { | |
return n1 * x * x; | |
} else if (x < 2 / d1) { | |
return n1 * (x -= 1.5 / d1) * x + 0.75; | |
} else if (x < 2.5 / d1) { | |
return n1 * (x -= 2.25 / d1) * x + 0.9375; | |
} else { | |
return n1 * (x -= 2.625 / d1) * x + 0.984375; | |
} | |
}, | |
easeInOutBounce: function (x) { | |
return x < 0.5 | |
? (1 - easings.easeOutBounce(1 - 2 * x)) / 2 | |
: (1 + easings.easeOutBounce(2 * x - 1)) / 2; | |
} | |
} | |
/** | |
* @callback EasingFunction | |
* @param {number} ratio | |
* @return {number} | |
*/ | |
/** | |
* @param {number|function} top | |
* @param {number} time | |
* @param {Easings|EasingFunction} [easing] - default "easeOutQuart" | |
*/ | |
export function smoothy(top, time, easing) { | |
return new Promise(resolve => { | |
let startTime = null; | |
let startTop = window.scrollY; | |
let easingFunction; | |
if (typeof easing === 'function') { | |
easingFunction = easing; | |
} else { | |
easing = easing || 'easeOutQuart'; | |
if (!(easing in easings)) { | |
throw new Error('no such easing ' + easing); | |
} | |
easingFunction = easings[easing]; | |
} | |
let cancelScroll = false; | |
const onWheelEvent = e => { | |
cancelScroll = true; | |
}; | |
document.body.addEventListener('wheel', onWheelEvent); | |
function scroller(dt) { | |
if (startTime === null) { | |
startTime = dt; | |
} | |
let delta = dt - startTime; | |
let ratio = Math.min(1.0, delta / time); | |
ratio = easingFunction(ratio); | |
let realTop; | |
if (typeof top === 'function') { | |
realTop = top(); | |
} else { | |
realTop = top; | |
} | |
let scrollTo = Math.round(startTop + (realTop - startTop) * ratio) | |
window.scrollTo(0, scrollTo); | |
if (!cancelScroll && ratio < 1.0) { | |
window.requestAnimationFrame(scroller); | |
} else { | |
document.body.removeEventListener('wheel', onWheelEvent); | |
resolve({cancelled: cancelScroll}); | |
} | |
} | |
window.requestAnimationFrame(scroller); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment