Last active
March 18, 2020 15:49
-
-
Save kahl-dev/641f0b5c80d3fbdf174dac4ee8d60fb1 to your computer and use it in GitHub Desktop.
smoothscroll.es6.js
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
/** | |
* Smooth Scroll Script | |
* URL: https://gist.github.com/patrickkahl/641f0b5c80d3fbdf174dac4ee8d60fb1 | |
* | |
* POLYFILLS: | |
* - https://github.com/iamdustan/smoothscroll | |
* - https://github.com/w3c/IntersectionObserver/tree/master/polyfill | |
*/ | |
import 'intersection-observer' | |
import 'core-js/features/promise' | |
import smoothscroll from 'smoothscroll-polyfill' | |
smoothscroll.polyfill() | |
const OFFSET_HEIGHT = 0 | |
const NOT_QUERY_SELECTORS = [ | |
'[href="#"]', | |
'[href="#0"]', | |
'[class*="mm-"]', | |
'[href*="mm-"]', | |
'[href="#nav-mobile"]', | |
'[href="#nav"]', | |
] | |
const ANCHOR_OBERVER_QS = 'data-anchor-observe' | |
const ANCHOR_OBSERVER_GROUP_QS = 'data-anchor-group' | |
const ACTIVE_CLASS = 'is-active' | |
const _scrollIntoView = (target, optParam) => { | |
const top = target.getBoundingClientRect().top - OFFSET_HEIGHT | |
const opt = Object.assign(optParam, { top }) | |
const scrollByPromise = resolve => { | |
let timer = null | |
const scrollListener = () => { | |
clearTimeout(timer) | |
timer = window.setTimeout(() => resolve(), 250) | |
if (parseInt(opt.top) === (window.scrollTop || window.pageYOffset)) { | |
window.removeEventListener('scroll', scrollListener) | |
clearTimeout(timer) | |
resolve() | |
} | |
} | |
scrollListener() | |
window.addEventListener('scroll', scrollListener) | |
window.scrollBy(opt) | |
} | |
return new Promise(resolve => { | |
new Promise(scrollByPromise).then(() => { | |
const top = target.getBoundingClientRect().top - OFFSET_HEIGHT | |
if (top !== 0) { | |
opt.top = top | |
new Promise(scrollByPromise).then(() => resolve()) | |
} else { | |
resolve() | |
} | |
}) | |
}) | |
} | |
const linkList = [].slice.call( | |
document.querySelectorAll( | |
`a[href*="#"]${NOT_QUERY_SELECTORS.reduce( | |
(str, qs) => (str += `:not(${qs})`), | |
'' | |
)}` | |
) | |
) | |
// Add smooth scroll to anchor on click | |
linkList.forEach(anchor => | |
anchor.addEventListener('click', e => { | |
const { pathname, hostname, hash } = anchor | |
if ( | |
location.pathname.replace(/^\//, '') === pathname.replace(/^\//, '') && | |
location.hostname === hostname | |
) { | |
let target = document.querySelector(hash) | |
target = target | |
? target | |
: document.querySelector(`[name=${hash.slice(1)}]`) | |
if (target) { | |
e.preventDefault() | |
_scrollIntoView(target, { | |
behavior: 'smooth', | |
}) | |
} | |
} | |
}) | |
) | |
// Add smooth scroll on page load | |
if (window.location.hash) { | |
const hash = window.location.hash | |
window.location.hash = '' | |
history.replaceState(null, null, '') | |
let target = document.querySelector(hash) | |
target = target ? target : document.querySelector(`[name=${hash.slice(1)}]`) | |
if (target) { | |
setTimeout(function() { | |
_scrollIntoView(target, { | |
behavior: 'smooth', | |
}).then(() => { | |
history.replaceState(null, null, hash) | |
}) | |
}, 500) | |
} | |
} | |
// Add active state for anchor link | |
const observeList = [].slice.call( | |
document.querySelectorAll(`[${ANCHOR_OBERVER_QS}]`) | |
) | |
if (observeList) { | |
const observeGroups = observeList.reduce((arr, item) => { | |
let index = arr.findIndex( | |
obj => obj.name === item.getAttribute(ANCHOR_OBSERVER_GROUP_QS) || null | |
) | |
if (index < 0) { | |
arr.push({ | |
name: item.getAttribute(ANCHOR_OBSERVER_GROUP_QS) || null, | |
items: [], | |
length: 0, | |
}) | |
index = arr.length - 1 | |
} | |
arr[index].items.push({ item }) | |
return arr | |
}, []) | |
observeGroups.forEach(group => { | |
const observer = new IntersectionObserver( | |
entries => { | |
entries.forEach(entry => { | |
const { intersectionRatio, isIntersecting, target } = entry | |
if (group.name) { | |
const cur = group.items.find(item => item.observeElem === target) | |
cur.isIntersecting = isIntersecting | |
const activeItem = group.items.find(item => item.isIntersecting) | |
if (activeItem) group.activeItem = activeItem.item | |
group.items.map(obj => obj.item.classList.remove(ACTIVE_CLASS)) | |
if (group.activeItem) group.activeItem.classList.add(ACTIVE_CLASS) | |
} else { | |
if (isIntersecting && intersectionRatio > 0) { | |
group.items[0].item.classList.add(ACTIVE_CLASS) | |
} else { | |
group.items[0].item.classList.remove(ACTIVE_CLASS) | |
} | |
} | |
}) | |
}, | |
{ | |
threshold: [0, 0.5, 1], | |
} | |
) | |
group.items.forEach(obj => { | |
const { item } = obj | |
const childLink = item.querySelector('a[href*="#"]') | |
const url = item.href || (childLink ? childLink.href : null) | |
if (url) { | |
const id = url.split('#')[1] | |
const elem = document.querySelector(`#${id}`) | |
if (elem) { | |
obj.observeElem = elem | |
observer.observe(elem) | |
} | |
} | |
}) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment