Last active
March 17, 2022 19:10
-
-
Save vinaypuppal/b7271ad84a0d69c9cfafaaa83afed199 to your computer and use it in GitHub Desktop.
Next.js smooth scroll
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
import React, { Children } from 'react' | |
import Router from 'next/router' | |
import smoothScroll from '../utils/smoothScroll' | |
// this HOC is taken from https://github.com/zeit/next.js/blob/master/lib/link.js and modified | |
export default class LinkSmoothScroll extends React.Component { | |
constructor (props) { | |
super(props) | |
this.linkClicked = this.linkClicked.bind(this) | |
} | |
linkClicked (e) { | |
e.preventDefault() | |
Router | |
.push(this.props.href) | |
.then(() => { | |
return smoothScroll(this.props.href) | |
}) | |
.then(() => { | |
this.props.done && this.props.done() | |
}) | |
.catch(err => { | |
this.props.onError && this.props.onError(err) | |
console.error(err) | |
}) | |
} | |
render () { | |
let { children } = this.props | |
if (typeof children === 'string') { | |
children = <a>{children}</a> | |
} | |
const child = Children.only(children) | |
const props = { onClick: this.linkClicked } | |
if (child.type === 'a' && !('href' in child.props)) { | |
props.href = this.props.href | |
} | |
return React.cloneElement(child, props) | |
} | |
} |
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
// Get the top position of an element in the document | |
const getTop = function (element, start) { | |
// return value of html.getBoundingClientRect().top ... IE : 0, other browsers : -pageYOffset | |
if (element.nodeName === 'HTML') return -start | |
return element.getBoundingClientRect().top + start | |
} | |
// ease in out function thanks to: | |
// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/ | |
const easeInOutCubic = function (t) { | |
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 | |
} | |
// calculate the scroll position we should be in | |
// given the start and end point of the scroll | |
// the time elapsed from the beginning of the scroll | |
// and the total duration of the scroll (default 500ms) | |
const position = function (start, end, elapsed, duration) { | |
if (elapsed > duration) return end | |
return start + | |
(end - start) * | |
easeInOutCubic( | |
elapsed / duration | |
) // <-- you can change the easing funtion there | |
// return start + (end - start) * (elapsed / duration); // <-- this would give a linear scroll | |
} | |
// we use requestAnimationFrame to be called by the browser before every repaint | |
// if the first argument is an element then scroll to the top of this element | |
// if the first argument is numeric then scroll to this location | |
// if the callback exist, it is called when the scrolling is finished | |
// if context is set then scroll that element, else scroll window | |
const smoothScroll = function (el, duration, callback, context) { | |
duration = duration || 500 | |
context = context || window | |
const start = context.scrollTop || window.pageYOffset | |
let end | |
if (typeof el === 'number') { | |
end = parseInt(el) - 60 | |
} else { | |
end = getTop(el, start) - 60 | |
} | |
const clock = Date.now() | |
const requestAnimationFrame = window.requestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
function (fn) { | |
window.setTimeout(fn, 15) | |
} | |
const step = function () { | |
const elapsed = Date.now() - clock | |
if (context !== window) { | |
context.scrollTop = position(start, end, elapsed, duration) | |
} else { | |
window.scroll(0, position(start, end, elapsed, duration)) | |
} | |
if (elapsed > duration) { | |
if (typeof callback === 'function') { | |
callback(el) | |
} | |
} else { | |
requestAnimationFrame(step) | |
} | |
} | |
step() | |
} | |
export default url => { | |
return new Promise(function (resolve, reject) { | |
const pattern = /^(\/#.+)|(.+(\/#.+))$/ | |
if (pattern.test(url)) { | |
const hash = pattern.exec(url).filter(item => item).pop() | |
const id = hash.replace(/\/?#/, '') | |
const el = document.getElementById(id) | |
if (el) { | |
smoothScroll(el, 600, resolve) | |
return | |
} | |
} | |
reject(new Error('Error: hash in URL is invalid or element not found!')) | |
}) | |
} |
This is meant to work on all browsers. The scroll-behavior: smooth
approach works on all major browsers except Safari.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This just works