stutrek / scrollmonitor

A simple and fast API to monitor elements as you scroll
MIT License
3.3k stars 243 forks source link

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

Open steappe opened 6 years ago

steappe commented 6 years ago

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:

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

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.

stutrek commented 6 years ago

I don't know much about how Vue works, but I suspect it has something to do with the virtual DOM swapping elements.

Is there a way to get Vue to give you the DOM element that currently represents the Vue component? If you get it right before your error is it the same as when your component was created?

I didn't make any of these so I can't speak to their quality, but there are a few packages that connect Vue and the scroll monitor.

steappe commented 6 years ago

I've played around with these packages, and unless I'm wrong the first two ones only work when the body is scrolled. The third one does more or less what I'm doing in my project.

According to what I can see in Chrome, the Vue's updated Hook is invoked after the div element has been moved in the real DOM.

I've tried what they suggest in their documentation:

updated: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been re-rendered
  })
}

I call scrollMonitor.recalculateLocations() in that function, but this is not triggering the visibility change callback set on the watcher of the moved element.

stutrek commented 6 years ago

Is the first one the only one that's incorrect or is the first one out of the viewport also incorrect?

steappe commented 6 years ago

When I invoke scrollMonitor.recalculateLocations(), I would say both. When I scroll, I get visibility change notifications about elements that are not in the viewport, as if the div element had not been moved.

When I do not invoke scrollMonitor.recalculateLocations(), the notifications are OK when I scroll. It's just that I don't receive a visibility change notification at the very moment the div element is moved.