Skip to content

Instantly share code, notes, and snippets.

@nitori
Created July 15, 2022 13:44
Show Gist options
  • Save nitori/d5a2b11c867d66eff99736ca8d573c6f to your computer and use it in GitHub Desktop.
Save nitori/d5a2b11c867d66eff99736ca8d573c6f to your computer and use it in GitHub Desktop.
JavaScript simple smooth scroll
// 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