Skip to content

Instantly share code, notes, and snippets.

@pascaldevink
Created April 13, 2012 20:53
Show Gist options
  • Select an option

  • Save pascaldevink/2380129 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

@pascaldevink could you explain how to use this please?

@SkyzohKey

SkyzohKey commented May 16, 2017

Copy link
Copy Markdown

@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
Copy Markdown

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

sp42 commented Jul 26, 2018

Copy link
Copy Markdown

Good job, man!

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