Skip to content

Instantly share code, notes, and snippets.

@kahl-dev
Last active March 18, 2020 15:49
Show Gist options
  • Save kahl-dev/641f0b5c80d3fbdf174dac4ee8d60fb1 to your computer and use it in GitHub Desktop.
Save kahl-dev/641f0b5c80d3fbdf174dac4ee8d60fb1 to your computer and use it in GitHub Desktop.
smoothscroll.es6.js
/**
* 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