Skip to content

Instantly share code, notes, and snippets.

@manifestinteractive
Created April 8, 2022 05:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manifestinteractive/6b23bc8ee28fd0bef6aa7e6b82f3e6b8 to your computer and use it in GitHub Desktop.
Save manifestinteractive/6b23bc8ee28fd0bef6aa7e6b82f3e6b8 to your computer and use it in GitHub Desktop.
Custom Scroll Helper to make listening to Page Level Scroll Events easier

Sample Usage

CSS

:root {
  --header-height: 100px;
}

// Helper Utility for Scroll Events
html.is-scrolling {
    header {
        transition: top 0.15s ease-in-out;
    }
}

html.scroll-direction-down {
    &.sticky-header {
        // Prevent Body Glitch from Header moving to Sticky
        body {
            padding-top: var(--header-height);
        }

        body.showstickybar {
            .addtocart-sticky-bar {
                top: -34px !important;
            }
        }

        header {
            position: fixed;
            top: calc(-1 * var(--header-height));
            left: 0;
            width: 100%;
            z-index: 10;
        }
    }
}

html.scroll-direction-up {
    &.sticky-header {
        // Prevent Body Glitch from Header moving to Sticky
        body {
            padding-top: var(--header-height);
        }

        header {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            z-index: 10;
        }
    }
}

JavaScript

// Listen for Scroll Update
window.addEventListener('scrollUpdate', function (evt) {
    console.log('Scroll Direction', evt.detail.direction);
    console.log('Last Scroll Position', evt.detail.last);
    console.log('Sticky Header Enabled', evt.detail.stickyHeader);
});

// Listen for Scroll Direction Change
window.addEventListener('scrollDirectionChange', function (evt) {
    console.log('Scroll Direction', evt.detail.direction);
    console.log('Last Scroll Position', evt.detail.last);
    console.log('Sticky Header Enabled', evt.detail.stickyHeader);
});

// Listen for Start of Page Scroll
window.addEventListener('scrollStart', function (evt) {
    console.log('Scroll Direction', evt.detail.direction);
    console.log('Last Scroll Position', evt.detail.last);
    console.log('Sticky Header Enabled', evt.detail.stickyHeader);
});

// Listen for Stop of Page Scroll
window.addEventListener('scrollStop', function (evt) {
    console.log('Scroll Direction', evt.detail.direction);
    console.log('Last Scroll Position', evt.detail.last);
    console.log('Sticky Header Enabled', evt.detail.stickyHeader);
});

// Listen for Change in Sticky Header
window.addEventListener('stickyHeaderChange', function (evt) {
    console.log('Scroll Direction', evt.detail.direction);
    console.log('Last Scroll Position', evt.detail.last);
    console.log('Sticky Header Enabled', evt.detail.stickyHeader);
});
'use strict';
/**
* Check if we can use Passive Event Listening
*/
function passiveSupport () {
let passive = false;
try {
window.addEventListener('test', null,
Object.defineProperty({}, 'passive', {
get: function() {
passive = {
passive: true
};
}
})
);
} catch(err) {}
return passive
}
module.exports = {
passiveSupport: passiveSupport
}
'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);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment