|
'use strict'; |
|
|
|
var passiveIfSupported = require('./helpers').passiveSupport(); |
|
|
|
class ScrollDirection { |
|
constructor () { |
|
// Element to Add CSS Classnames |
|
this.$elm = document.documentElement; |
|
|
|
// Header Element ( NOTE: Change this if needed ) |
|
this.$header = document.querySelector('header'); |
|
|
|
// Element to Listen to Scroll Events |
|
this.$target = window; |
|
|
|
// Current Scroll Direction |
|
this.direction = null; |
|
|
|
// Track Header Height |
|
this.headerHeight = null; |
|
|
|
// Detect if this is IE11 ( oldest supported browser ) |
|
this.isIE11 = false; |
|
|
|
// Target Element is Scrolling |
|
this.isScrolling = false; |
|
|
|
// Last Scroll Position |
|
this.last = 0; |
|
|
|
// Show CSS Header |
|
this.stickyHeader = false; |
|
|
|
// Timeout Event for Scroll Handler |
|
this.timeout = null; |
|
|
|
// How far to allow Scroll before Triggering Scroll Events |
|
this.threshold = 10; |
|
|
|
// Initialize Scroll Handlers |
|
this.init(); |
|
} |
|
|
|
init () { |
|
// Detect IE11 |
|
this.isIE11 = !!window.MSInputMethodContext && !!document.documentMode; |
|
|
|
// Add Event Listeners |
|
this.listener = this.detectDirection.bind(this); |
|
this.resize = this.detectResize.bind(this); |
|
|
|
this.$target.addEventListener('scroll', this.listener, passiveIfSupported); |
|
this.$target.addEventListener('resize', this.resize, passiveIfSupported); |
|
|
|
// Do initial Size Detection |
|
this.detectResize(); |
|
} |
|
|
|
detectDirection () { |
|
// Get Scroll Position |
|
const scrolled = this.$target.scrollY || this.$target.scrollTop || this.$target.pageYOffset; |
|
|
|
// Check if New Scroll Position Breaks Threshold |
|
if (Math.abs(scrolled - this.last) >= this.threshold) { |
|
const currentDirection = (scrolled > this.last) ? 'down' : 'up'; |
|
|
|
// Check if Scrolling |
|
if (scrolled !== this.last && !this.isScrolling) { |
|
this.onScrollStart(); |
|
} |
|
|
|
// Check if Header exists and if we should trigger Sticky Header |
|
if (this.headerHeight && scrolled > this.headerHeight && !this.stickyHeader) { |
|
this.stickyHeader = true; |
|
this.$elm.classList.add('sticky-header'); |
|
|
|
// Fire Custom Event `stickyHeaderChange` |
|
this.$target.dispatchEvent(new CustomEvent('stickyHeaderChange', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} else if (this.headerHeight && scrolled < this.headerHeight && this.stickyHeader) { |
|
this.stickyHeader = false; |
|
this.$elm.classList.remove('sticky-header'); |
|
|
|
// Fire Custom Event `stickyHeaderChange` |
|
this.$target.dispatchEvent(new CustomEvent('stickyHeaderChange', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} |
|
|
|
// Detect Direction Change |
|
if (this.direction !== currentDirection) { |
|
this.onDirectionChange(currentDirection); |
|
} |
|
|
|
// Check if IE11 and set CSS styles inline since CSS vars will not work ( not as smooth, but only option ) |
|
if (this.isIE11 && this.$header) { |
|
// Check if Sticky Header is Enabled |
|
if (this.stickyHeader) { |
|
document.body.style.paddingTop = this.$header.offsetHeight + 'px'; |
|
|
|
if (this.direction === 'down') { |
|
this.$header.style.top = (-1 * this.$header.offsetHeight) + 'px'; |
|
} else { |
|
this.$header.style.top = 0; |
|
} |
|
} else { |
|
document.body.style.paddingTop = '0px'; |
|
} |
|
} |
|
|
|
// Update Last Scroll Position |
|
this.last = scrolled; |
|
|
|
// Fire Custom Event `scrollUpdate` |
|
this.$target.dispatchEvent(new CustomEvent('scrollUpdate', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} |
|
|
|
// Clear Last Timeout before Recreating it |
|
if (this.timeout) { |
|
clearTimeout(this.timeout); |
|
} |
|
|
|
// Fire Stop Scroll Event shortly after Scrolling Stops |
|
this.timeout = setTimeout(this.onScrollStop.bind(this), 500); |
|
} |
|
|
|
detectResize () { |
|
// Get Header Height |
|
if (this.$header) { |
|
this.headerHeight = this.$header.offsetHeight; |
|
this.$elm.style.setProperty('--header-height', this.headerHeight + 'px'); |
|
} |
|
} |
|
|
|
onDirectionChange (direction) { |
|
this.direction = direction; |
|
|
|
// Update Scroll Classes |
|
this.$elm.classList.add('scroll-direction-' + this.direction); |
|
this.$elm.classList.remove('scroll-direction-' + ( |
|
this.direction == 'down' ? 'up' : 'down' |
|
)); |
|
|
|
// Fire Custom Event `scrollDirectionChange` |
|
this.$target.dispatchEvent(new CustomEvent('scrollDirectionChange', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} |
|
|
|
onScrollStart () { |
|
this.$elm.classList.add('is-scrolling'); |
|
this.isScrolling = true; |
|
|
|
// Fire Custom Event `scrollStart` |
|
this.$target.dispatchEvent(new CustomEvent('scrollStart', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} |
|
|
|
onScrollStop () { |
|
this.$elm.classList.remove('is-scrolling'); |
|
this.isScrolling = false; |
|
this.timeout = null; |
|
|
|
// Fire Custom Event `scrollStop` |
|
this.$target.dispatchEvent(new CustomEvent('scrollStop', { |
|
detail: { |
|
direction: this.direction, |
|
last: this.last, |
|
stickyHeader: this.stickyHeader |
|
} |
|
})); |
|
} |
|
} |
|
|
|
module.exports = () => { |
|
// Event Listeners only work on initial page load on some browsers, so let's check DOM State |
|
var isReady = document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll); |
|
|
|
// Callback to Start up Scroll Detection |
|
var callback = () => { |
|
new ScrollDirection(); |
|
}; |
|
|
|
if (isReady) { |
|
// DOM already loaded, addEventListener will not work, go ahead and trigger callback |
|
callback(); |
|
} else { |
|
// DOM not loaded, let's addEventListener |
|
document.addEventListener('DOMContentLoaded', callback); |
|
} |
|
}; |