Skip to content

Instantly share code, notes, and snippets.

@pascaldevink
Created April 13, 2012 20:53
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save pascaldevink/2380129 to your computer and use it in GitHub Desktop.
Save pascaldevink/2380129 to your computer and use it in GitHub Desktop.
ScrollSpy in pure javascript
/*
Copyright (C) 2021 Pascal de Vink (Tweakers.net)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var ScrollSpy = (function()
{
var elements = {};
function init()
{
if (document.addEventListener)
{
document.addEventListener("touchmove", handleScroll, false);
document.addEventListener("scroll", handleScroll, false);
}
else if (window.attachEvent)
{
window.attachEvent("onscroll", handleScroll);
}
}
function spyOn(domElement)
{
var element = {};
element['domElement'] = domElement;
element['isInViewPort'] = true;
elements[domElement.id] = element;
}
function handleScroll()
{
var currentViewPosition = document.documentElement.scrollTop ? document.documentElement.scrollTop: document.body.scrollTop;
for (var i in elements) {
var element = elements[i];
var elementPosition = getPositionOfElement(element.domElement);
var usableViewPosition = currentViewPosition;
if (element.isInViewPort == false)
{
usableViewPosition -= element.domElement.clientHeight;
}
if (usableViewPosition > elementPosition)
{
fireOutOfSightEvent(element.domElement);
element.isInViewPort = false;
}
else if (element.isInViewPort == false)
{
fireBackInSightEvent(element.domElement);
element.isInViewPort = true;
}
};
}
function fireOutOfSightEvent(domElement)
{
fireEvent('ScrollSpyOutOfSight', domElement);
}
function fireBackInSightEvent(domElement)
{
fireEvent('ScrollSpyBackInSight', domElement);
}
function fireEvent(eventName, domElement)
{
if (document.createEvent)
{
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, true);
event.data = domElement;
document.dispatchEvent(event);
}
else if (document.createEventObject)
{
var event = document.createEventObject();
event.data = domElement
event.expando = eventName;
document.fireEvent('onpropertychange', event);
}
}
function getPositionOfElement(domElement)
{
var pos = 0;
while (domElement != null)
{
pos += domElement.offsetTop;
domElement = domElement.offsetParent;
}
return pos;
}
return {
init: init,
spyOn: spyOn
};
});
@featherbear
Copy link

@pascaldevink could you explain how to use this please?

@SkyzohKey
Copy link

SkyzohKey commented May 16, 2017

@bearbear12345 and others:

var scrollSpy = new ScrollSpy();
scrollSpy.init();

var el = document.querySelector('#nav');
scrollSpy.spyOn(el);

/* Element is visible again on the screen. */
document.addEventListener('ScrollSpyBackInSight', function (e) {
    /* ie. Make the nav to float. */
  el.style.position = 'relative';
});

/* Element is not visible anymore on the screen. */
document.addEventListener('ScrollSpyOutOfSight', function (e) {
  /* ie. Make the nav to fix. */
  el.style.position = 'fixed';
  el.style.top = '0px';
});

I can be wrong but that's how i'd use it.

@ShahinSorkh
Copy link

And this is ES6 version:

const scrollSpy = new function () {
  const elements = {}

  const init = () => {
    if (document.addEventListener) {
      document.addEventListener('touchmove', handleScroll, false)
      document.addEventListener('scroll', handleScroll, false)
    }
    else if (window.attachEvent) {
      window.attachEvent('onscroll', handleScroll)
    }
  }

  const spyOn = domElement => {
    const element = {}
    element['domElement'] = domElement
    element['justGotIntoViewPort'] = true
    elements[domElement.id] = element
  }

  const handleScroll = () => {
    const currentViewPosition = document.documentElement.scrollTop
      ? document.documentElement.scrollTop
      : document.body.scrollTop

    for (let element of elements) {
      const elementPosition = getPositionOfElement(element.domElement)

      let usableViewPosition = currentViewPosition
      if (!element.isInViewPort) {
        usableViewPosition -= element.domElement.clientHeight
      }

      if (usableViewPosition > elementPosition) {
        fireOutOfSightEvent(element.domElement)
        element.isInViewPort = false
      } else if (!element.isInViewPort) {
        fireBackInSightEvent(element.domElement)
        element.isInViewPort = true
      }
    }
  }

  const fireOutOfSightEvent = domElement => {
    fireEvent('ScrollSpyOutOfSight', domElement)
  }

  const fireBackInSightEvent = domElement => {
    fireEvent('ScrollSpyBackInSight', domElement)
  }

  const fireEvent = (eventName, domElement) => {
    if (document.createEvent) {
      const event = document.createEvent('HTMLEvents')
      event.initEvent(eventName, true, true)
      event.data = domElement
      document.dispatchEvent(event)
    } else if (document.createEventObject) {
      const event = document.createEventObject()
      event.data = domElement
      event.expando = eventName
      document.fireEvent('onpropertychange', event)
    }
  }

  const getPositionOfElement = domElement => {
    let pos = 0
    while (domElement) {
      pos += domElement.offsetTop
      domElement = domElement.offsetParent
    }
    return pos
  }

  return {init, spyOn}
}()

scrollSpy.init()
export { scrollSpy }

usage

import { scrollSpy } from './ScrollSpy'
// // // // // // // // stuff
scrollSpy.spyOn(foo)
// // // // // then add listeners and whatever

@sp42
Copy link

sp42 commented Jul 26, 2018

Good job, man!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment