5

How would I pass an extra argument to the IntersectionObserver? I am trying to create a lazyload plugin for Vue. It works fine like this but I also want to be able to call a provided directive function.

const lazyload = {
    install(Vue) {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.intersectionRatio > 0) {
                    console.log('In viewport');
                }
            });
        });

        Vue.directive('lazy', {
            inserted(element) {
                observer.observe(element);
            }
        });
    }
};

This would mean that in the inserted function I would set binding as the second argument.

inserted(element, binding)

How would I pass this binding though so that I could use it within my IntersectionObserver callback?

In the end it should be able to look something like this:

<div class="col-sm-6" v-lazy="fire">
Stephan-v
  • 19,255
  • 31
  • 115
  • 201

2 Answers2

0

You could access the directive's arguments with binding.value:

// html
<div v-lazy="0.5"></div>

// script
Vue.directive('lazy', {
  inserted(el, binding) {
    console.log(binding.value) // 0.5
  }
})

It probably makes sense to create a new IntersectionObserver for each directive usage. For instance, you might want one div to have a different scroll threshold from another div. Within the inserted callback, I would create the observer with options specified in the directive arguments:

const makeObserver = options => new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    console.log({entry, ratio: options.threshold})
    if (entry.intersectionRatio >= options.threshold) {
      console.log('In viewport', {entry, ratio: options.threshold});
    }
  });
}, options);

Vue.directive('lazy', {
  inserted(el, binding) {
    const options = binding.value || {}
    const observer = makeObserver(options)
    observer.observe(el)
  }
})

Then, you could use that directive to create multiple divs with different parameters for IntersectionObserver:

<div style="height: 100px; background: gray" v-lazy="{root: null, threshold: 0.1}"></div>
<div style="height: 200px; background: lightgray" v-lazy="{root: null, threshold: 1}"></div>

demo

tony19
  • 125,647
  • 18
  • 229
  • 307
0

If you actually want to pass the values as an extra argument you could use Function.bind, but you'd have to create a separate IntersectionObserver for each directive invocation.

I'd recommend a WeakMap here, and have the callback check the map for the proper handler. Something like:

const lazyload = {
  install(Vue) {
    const handlers = new WeakMap();
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          console.log('In viewport');
          handlers.get(entry.target)();
        }
      });
    });

    Vue.directive('lazy', {
      inserted(element, binding) {
        handlers.set(element, binding.value);
        observer.observe(element);
      }
    });
  }
};

(I haven't used Vue, so I'm not sure this will work verbatim)

As an aside, I changed entry.intersectionRatio > 0 to entry.isIntersecting. This might be a bug, but I've seen in Chrome 65 that when scrolling very slowly, you can get entry.intersectionRatio === 0 && entry.isIntersecting === true: https://jsfiddle.net/3jgm0ch7/2/

FellowMD
  • 2,142
  • 3
  • 16
  • 15