Code Monkey home page Code Monkey logo

scrollmonitor's Introduction

scrollMonitor

The scroll monitor allows you to receive events when elements enter or exit a viewport. It does this using watcher objects, which watch an element and trigger events. Watcher objects also contain information about the element they watch, including the element's visibility and location relative to the viewport. If your scroll container is an element other than the body you can create a container that creates watchers.

The scroll monitor was designed to be very fast. On each scroll event the DOM is only touched twice, once to find the document height and again to find the viewport top. No variables are declared, nor are any objects, arrays, or strings created. Watchers are very cheap. Create them liberally.

The code is vanilla javascript and has no external dependencies, however the script cannot be put in the head.

Also see the React hooks, React component and the parallax library.

Basic Usage

When the body scrolls

var scrollMonitor = require("scrollmonitor"); // if you're old school you can use the scrollMonitor global.
var myElement = document.getElementById("itemToWatch");

var elementWatcher = scrollMonitor.create( myElement );

elementWatcher.enterViewport(function() {
    console.log( 'I have entered the viewport' );
});
elementWatcher.exitViewport(function() {
    console.log( 'I have left the viewport' );
});

For a scroll container

var containerElement = document.getElementById("container");

var containerMonitor = scrollMonitor.createContainer(containerElement);
// this containerMonitor is an instance of the scroll monitor
// that listens to scroll events on your container.

var childElement = document.getElementById("child-of-container");
var elementWatcher = containerMonitor.create(childElement);

elementWatcher.enterViewport(function() {
    console.log( 'I have entered the viewport' );
});
elementWatcher.exitViewport(function() {
    console.log( 'I have left the viewport' );
});

Note: an element is said to be in the viewport if it is scrolled into its parent, it does not matter if the parent is in the viewport.

Demos

Watcher Objects

Create watcher objects with scrollMonitor.create( watchItem ). An optional second argument lets you receive events before or after this element enters the viewport. See "Offsets".

watchItem can be one of the following:

  • DOM Element - the watcher will watch the area contained by the DOM element.
  • Object - obj.top and obj.bottom will be used for watcher.top and watcher.bottom.
  • Number - the watcher will watch a 1px area this many pixels from the top. Negative numbers will watch from the bottom.
  • jQuery object - it will use the first DOM element.
  • NodeList or Array - it will use the first DOM element.
  • string - it will use the string as a CSS selector and watch the first match.

Watchers are automatically recalculated on the first scroll event after the height of the document changes.

Events

Element watchers trigger six events:

  • visibilityChange - when the element enters or exits the viewport.
  • stateChange - similar to visibilityChange but is also called if the element goes from below the viewport to above it in one scroll event or when the element goes from partially to fully visible or vice versa.
  • enterViewport - when the element enters the viewport.
  • fullyEnterViewport - when the element is completely in the viewport [1].
  • exitViewport - when the element completely leaves the viewport.
  • partiallyExitViewport - when the element goes from being fully in the viewport to only partially [2].
  1. If the element is larger than the viewport fullyEnterViewport will be triggered when the element spans the entire viewport.
  2. If the element is larger than the viewport partiallyExitViewport will be triggered when the element no longer spans the entire viewport.

Properties

  • elementWatcher.isInViewport - true if any part of the element is visible, false if not.
  • elementWatcher.isFullyInViewport - true if the entire element is visible [1].
  • elementWatcher.isAboveViewport - true if any part of the element is above the viewport.
  • elementWatcher.isBelowViewport - true if any part of the element is below the viewport.
  • elementWatcher.top - distance from the top of the document to the top of this watcher.
  • elementWatcher.bottom - distance from the top of the document to the bottom of this watcher.
  • elementWatcher.height - top - bottom.
  • elementWatcher.watchItem - the element, number, or object that this watcher is watching.
  • elementWatcher.offsets - an object that determines the offsets of this watcher. See "Offsets".
  1. If the element is larger than the viewport isFullyInViewport is true when the element spans the entire viewport.

Methods

  • elementWatcher.on/off/one - the standard event functions.
  • elementWatcher.recalculateLocation - recalculates the location of the element in relation to the document.
  • elementWatcher.destroy - removes this watcher and clears out its event listeners.
  • elementWatcher.lock - locks this watcher at its current location. See "Locking".
  • elementWatcher.unlock - unlocks this watcher.

These methods are automatically called by the scrollMonitor, you should never need them:

  • elementWatcher.update - updates the boolean properties in relation to the viewport. Does not trigger events.
  • elementWatcher.triggerCallbacks - triggers any callbacks that need to be called.

Locking

Sometimes you want to change the element you're watching, but want to continue watching the original area. One common use case is setting position: fixed on an element when it exits the viewport, then removing positioning when it when it reenters.

var watcher = scrollMonitor.create( $element );
watcher.lock(); // ensure that we're always watching the place the element originally was

watcher.exitViewport(function() {
    $element.addClass('fixed');
});
watcher.enterViewport(function() {
    $element.removeClass('fixed');
});

Because the watcher was locked on the second line, the scroll monitor will never recalculate its location.

Offsets

If you want to trigger an event when the edge of an element is near the edge of the viewport, you can use offsets. The offset is the second argument to scrollMonitor.create.

This will trigger events when an element gets within 200px of the viewport:

scrollMonitor.create( element, 200 )

This will trigger when the element is 200px inside the viewport:

scrollMonitor.create( element, -200 )

If you only want it to affect the top and bottom differently you can send an object in.

scrollMonitor.create( element, {top: 200, bottom: 50})

If you only want it to affect the top and not the bottom you can use only one property in.

scrollMonitor.create( element, {top: 200})

scrollMonitor Module

Methods

  • scrollMonitor.createContainer( containerEl ) - returns a new ScrollMonitorContainer that can be used just like the scrollMonitor module.
  • scrollMonitor.create( watchItem, offsets ) - Returns a new watcher. watchItem is a DOM element, jQuery object, NodeList, CSS selector, object with .top and .bottom, or a number.
  • scrollMonitor.update() - update and trigger all watchers.
  • scrollMonitor.recalculateLocations() - recalculate the location of all unlocked watchers and trigger if needed.

Properties

  • scrollMonitor.viewportTop - distance from the top of the document to the top of the viewport.
  • scrollMonitor.viewportBottom - distance from the top of the document to the bottom of the viewport.
  • scrollMonitor.viewportHeight - height of the viewport.
  • scrollMonitor.documentHeight - height of the document.

Contributing

There is a set of unit tests written with Mocha + Chai that run in testem. Getting them running is simple:

npm install
npm test

then open http://localhost:7357

scrollmonitor's People

Contributors

afc163 avatar alokreddyk avatar emkay avatar gildas-lormeau avatar methodgrab avatar oroce avatar patrickjs avatar stutrek avatar tamebadger avatar theorm avatar tlee avatar undirectlookable avatar weotch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scrollmonitor's Issues

Callback uses wrong context when the event already applies

On line 243 callback is called, but it is called differently when the event happens on scroll.

Shouldn't it be called within the ElementWatcher context ?

It is a little disturbing that sometimes you have access to "this.watchItem" member and sometimes you don't.

Use of Intersection Observer API

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

Would be nice if scrollMonitor can use this native browser API for monitoring. And with a fallback for older browsers.

Scroll does not recalculate watcher locations

Upon scroll, the calculateViewport is called. However, since the document height did not change, the watchers positions will not be updated. As a result the values of isInViewport does not get updated. I have verified all this through the debugger, and I am not certain how this is working for other people.

I am using Chrome (v 51.0.2704.103 64-bit) on Mac if that makes any difference.

Only one element triggers events

I'm currently looking into implementing scrollMonitor. The promised functionality is exactly what I need. However, I cannot get any event fired more than once. Even worse, Only the element that is in view on page load, triggers an event.

My HTML looks like this:

<body>
    <div class="section-container">
        <div class="section" id="section1">...</div>
        <div class="section" id="section2">...</div>
        <div class="section" id="section3">...</div>
        ...
    </div>
</body>

Each .section element has the following styling:

.section {
    width: 100vw;
    height: 100vh;
    box-sizing : border-box;
    position   : relative;
}

The javascript:

var sectionElements = $('.section');

sectionElements.each(function (index, element) {
    var sectionWatcher = scrollMonitor.create(element);
    var elementId = element.getAttribute('id');

    sectionWatcher.fullyEnterViewport(function() {
        console.log(elementId + ' fullyEnterViewport')
    });

    sectionWatcher.enterViewport(function () {
        console.log(elementId + ' has entered the viewport');
    });

    sectionWatcher.stateChange(function () {

        console.log(elementId + ' state has changed');
    });
});

Now, when I load the page, my console prints this:

section1 fullyEnterViewport
section1 has entered the viewport

Which makes sense. But scrolling doesn't yield any other console messages. At all. Frustrating.

There must be something I'm overlooking or forgot, but I can't see it.
Anyone has an idea?

scrollMonitor is not defined

I'm not using any kind of module loading or require - and scrollMonitor and window.scrollMonitor come back undefined for me - can you use this w/out those libraries?

thanks!

In Viewport calculation is off by one

Check out these examples where I have created two boxes:

In the first example, they are both have heights of 100vh. Note that they both register as in viewport even though the second box should be pushed out of it by the first, which should completely fill the viewport.

In the second example, the boxes have heights of calc(100vh + 1px). Now, the second box correctly registers as being out of the viewport.

using scrollMonitor.recalculateLocations() after a DOM change - how to?

I'm using Vue.js in a project where I have a scroll container and its child elements.

In the scroll container:

  mounted: function () {
    this.monitor = scrollMonitor.createContainer(this.$el);
  },

  updated: function () {
    this.monitor.recalculateLocations();
  }

In the child elements:

  mounted () {
    this.watcher = this.monitor.create(this.$el);
    this.inViewport = this.watcher.isInViewport;

    this.watcher.visibilityChange(() => {
      this.inViewport = this.watcher.isInViewport;
    });
  },

  beforeDestroy () {
    this.watcher.destroy();
  }

The scroll container passes the scroll monitor to its children through a Vue property.

This works perfectly fine, until an element is moved from one position in the list to another. This is causing the associated DOM element to be moved in the scroll container, and the updated Hook function is called. This Hook function invokes monitor.recalculateLocations() to have the children watchers recalculate their locations and triggers events. But this is not happening (if the element is moved from a position where it is not visible to a position where it is visible in the scroll container, no event is fired), and I would like to understand why.

Example:

  • Div for element 1 (in viewport)
  • Div for element 2 (in viewport)
  • etc.
  • Div for element n (not in viewport)

The element n is moved to top, resulting in the following changes in the DOM:

  • Div for element n (in viewport)
  • Div for element 1 (in viewport)
  • etc.
  • Div for element n-1 (not in viewport)

The Div for element n is now visible in it's parent scroll container. However, when the scrollMonitor.recalculateLocations() is called, the watcher created for element n does not trigger the visibility change callback.

IE11 error on removed elements

Watchers with their watchItem removed by eg. $('.watched-element').remove() will run in to an error on IE11, when trying to access the .getBoundingClientRect()

.off and .destroy fn's broke?

var monitor = scrollMonitor.create(someElement);
.on('enterViewport', onEnterFunction); //works great

.off('enterViewport') //doesn't work //keeps calling .on('enterViewport', onEnter);

Same with destroy - doesn't work.

Thanks for any feedback

Github pages examples broken

It seems to be because the page loads over https but it's including a reference to a jQuery file at an http endpoint.

enterViewport called for all elements when watcher created.

I've created a very simple gist to illustrate the issue. The console shows that the enterViewport callback is called for each of the elements even though only the first is in the viewport.

The exitViewport callbacks are also not being called correctly; scrolling the second element into view and then out of it never triggers its exitViewport callback.

Always create global scrollMonitor reference

Other libraries that depend on scrollMonitor shouldn't have to be AMD-aware. However, currently, if you have a non-AMD-aware library that depends on scrollMonitor (using window.scrollMonitor), it can't be shimmed because scrollMonitor will never add itself to the window object.

modify the webpack and eslint

I think the webpack config and eslint is not good enough, I can modify it, but it can make the project's code change to lot, Do you want I do this?

horizontal scrolling

Looks nice! Can you use it also for horizontal instead of vertical scrolling?

Percentage offset values

Are there plans for implementing the offset values with pixel or with percentages?
This allows a more similar behavior on different screen sizes.

Use another wrapper than window/document

I'm working with this GREAT plugin. I'm really love it (some are WIP):
http://codepen.io/myconcretelab/pen/wkduG
http://codepen.io/myconcretelab/pen/ctuoa
http://codepen.io/myconcretelab/pen/aHkih

I'm asking if is it possible to easily pass another wrapper into the constructor. it seems that if elements are in a div, somewhere in the page i will nee to set all these properties :
scrollMonitor.viewportTop, scrollMonitor.viewportBottom, scrollMonitor.viewportHeight and scrollMonitor.documentHeight ?
Is it possible to add this feature ?

Init ScrollMonitor after ajax page load

Hello,

I set up a little script to init ScrollMonitor with Masonry and ImagesLoaded to have a full Lazy Loaded Masonry. Everything works as expected when the page loads :)

But, when i navigate to an another page, then i go back to the curent masonry/scrollmonitor page, all watchers are OK, but elementWatcher.enterViewport() does not fire anything. I have to scroll to launch elementWatcher.enterViewport, i tried elementWatcher.update() and elementWatcher.triggerCallbacks() as well, but both methods doesn't do anything.

I'm using Barba.js to handle ajax pages. Here is a look of what i've done:

masonry.js https://gist.github.com/proov/94c3955de351bff8cfb45d38a84d11b2
ajax-pages.js https://gist.github.com/proov/eff481d5fd6156c7753ade272343a146

I'm a designer, not a developper, be indulgent with my code :D

Any Idea ?

Many thanks! :)

Helper to monitor the topmost element on the screen

I'm looking to use scrollMonitor to monitor the topmost element displayed on the screen over time.

The way I'm currently doing it is, I consider an element the topmost on the screen if:

  • I scrolled down and the element started to partially exit the viewport,
  • or I scrolled up, and the element fully entered the viewport.

The code I'm using for this looks like:

$('span.s').each(function(index, elem) {
    var elementWatcher = scrollMonitor.create(elem);

    elementWatcher.partiallyExitViewport(function(scroll) {
        if (scrollDirection(scroll) < 0) {
            highlightSentence(elem);
        }
    });
    elementWatcher.fullyEnterViewport(function(scroll) {
        if (scrollDirection(scroll) > 0) {
            highlightSentence(elem);
        }
    });
});

where scrollDirection looks something like:

var lastScrollTop = 0;
var scrollDirection = function(e) {
    var st = $(this).scrollTop();
    var oldScrollTop = lastScrollTop;
    lastScrollTop = st;
    if (st > oldScrollTop) {
        return -1;
    } else if (st < oldScrollTop) {
        return 1;
    }
    else {
        return 0;
    }
};

Does this approach seems correct? Am I missing something that would make my life easier? If not, would it be interesting to write a helper to support this use case?

scrollMonitor.js.map errors

I may be using the package incorrectly, so please advise if so.

However, the packaged scrollMonitor.js has a reference to scrollMonitor.js.map. Webpack pulls in the main JS file but not the map, and so on every page I get error saying that it couldn't find or parse the map file.

Unless I should somehow be configuring my Webpack to look in minified JS files and pull our relative map paths and bundle them to (or it maybe it should be default?).

no version?

I can't find any version infomation in the repo, and also can't find package.json and tags.

Please add them.Thx!

Not working after initial load when using Turbolinks

Im having issues with scrollMonitor and Turbolinks. On initial load the scrollMonitor watchers work and fire correctly but after visiting another page they cease to work. I initially used the Turbolinks "no-cache" tag to stop caching but it to no avail. I have also used "recalculateLocations" to try and re-initialise the positions but still they will not fire.

The only way it will work is with a full page reload but then Turbolinks features become redundant.

Unminified version

Hi,

Very basic question but where may I find the not minified/uglyfied version of scrollMonitor ?

Many thanks

Throttle scroll events support?

I found scrollMonitor hitting performance of webpage and decrease FPS.

Are there any way to set throttle on enterViewport/exitViewport events ?

Regular event for scrolling on tablets (iOS)

I've tested the script on the iPad (no other tablet systems).
The downside here is that it only fires the event, after the scrolling ended – so 1-2 seconds delay because of the deceleration phase.
Is there a way to send an event during the active scrolling phase on iOS?
Thanks so far for your good work!

Add willEnterViewport method

This would be helpful to prep the element before it comes in to view. I wonder if one tracking viewport height above and below would be helpful to monitor this change.

How to fix another pages issue?

In home page working fine. But other pages I got this issue. How can I fix it?

Uncaught TypeError: Cannot read property 'nodeName' of null at o.recalculateLocation (app.min.js?ver=1.0:5) at new o (app.min.js?ver=1.0:5) at r.create (app.min.js?ver=1.0:5) at HTMLDocument.<anonymous> (app.min.js?ver=1.0:6) at i (jquery.js?ver=1.12.4:2) at Object.fireWith [as resolveWith] (jquery.js?ver=1.12.4:2) at Function.ready (jquery.js?ver=1.12.4:2) at HTMLDocument.K (jquery.js?ver=1.12.4:2)

Bug when reloading a page

When using fullyEnterViewport and partiallyExitViewport together, they both trigger one after another if the user is scrolled into the watched element and then refreshes/reloads the page. Refreshing the page causes the browser to scroll automatically to the same scroll position.

Please watch the following video
https://www.youtube.com/watch?v=Yah362YmCvk&feature=youtu.be

Note: In the codepen you'll have to scroll the output area so that the element with the red background covers the entire view port and then right click the frame and select 'reload frame' (in chrome)
The javascript which initializes scrollMonitor is at the bottom.
The codepen used in the video is at the following link.
http://codepen.io/anon/pen/epYmxW

Dependencies

Is there a particular reason to include the karma modules as dependencies rather than devDependencies?

As far as I can tell they aren't required to use scrollMonitor with browserify when installing it with npm. Building each karma module is slow and actually fails on Node 0.12.0 so if they aren't required dependencies I think it would be preferable to move them to devDependencies.

beget?

Why beget instead of something like watch?

Beget sounds like something from bible class and I think is the wrong use of the word.

Uncaught TypeError: Cannot read property 'scrollHeight' of null

Using jQuery 1.10.2, you get the following error from jquery.js

Uncaught TypeError: Cannot read property 'scrollHeight' of null
at jquery-1.10.2.js:9754
at Function.access (jquery-1.10.2.js:900)
at jQuery.fn.init.jQuery.fn.(anonymous function) [as height] (../Scripts/jquery-1.10.2.js:9737:18)

question

Hi I have multiple elements which have the same animation.
Can I use getElementsByClassName instead of getElementById like this

var myElements = document.getElementsByClassName("itemsToWatch");

var elementsWatcher = scrollMonitor.create( myElements );

Allow option for debouncing scroll monitor.

Currently since scrollMonitor must be instantiated and isn't a singleton it's very difficult to use something like _.debounce to limit the time it takes for the scroll watcher to fire. This is pretty common for people wanting to mess around with scroll events so I think it'd be a good feature to add.

An example of what I'm talking about can be shown in this code pen. http://codepen.io/anon/pen/mAGkyE

Sticky element rely on viewport/contentHeight, even if it's out of viewport

Really appreciate Your efforts and a nice approach to manage scroll listener. 👍

Ref - https://github.com/stutrek/scrollMonitor/blob/master/src/container.js#L168
As it rely on viewport/contentHeight, it cause an issue when we have multiple containers with different heights, So sticky element does not "recalculateLocation" even it's out of viewport.

I tried to explain the problem with a JSFiddle Demo

This Problem fix, if i remove remove a condition but that maybe a wrong fix.

So is this a bug or i am missing something?

Kindly ask if You need more detail. Thanks

Demos do not work

require.js fails to download, so all your demo examples time out.

It might be better idea to use CDN instead of hotlinking directly to the site's own version. require.js is available, for example, at http://cdnjs.com/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.