R
R
Renhor2019-04-23 20:31:21
Vue.js
Renhor, 2019-04-23 20:31:21

Vue. What is the best way to implement the visibility of an element on the screen?

Good day!
There is a HomePage.vue page, which has several DOM elements that should be displayed when they appear in the screen's viewport.
There is a simple visibility check function

function isVisible(el) {
  let elTop = el.offsetTop;
  let rect = el.getBoundingClientRect();

  return !(elTop <= rect.top || rect.bottom <= 0);
}

First I wanted to create a custom directive v-onscreen:delete="'translatedRight'"
Argument :delete - if you need to delete the class
Value translatedRight - the class that we delete / add
It would be no problem to create in the directive in the bind hook
let onscreen = {
  bind(el, options) {
    let className = options.value;
    let deleteClass = options.arg === "delete";

    window.addEventListener("scroll", () => {
      if (isVisible(el)) {
        if (deleteClass) {
          el.classList.remove(className)
        } else {
          el.classList.add(className)
        }
      } else {
        if (deleteClass) {
          el.classList.add(className)
        } else {
          el.classList.remove(className)
        }
      }
    });
  },
  unbind() {
    // ???
  }
};

But how to remove this wiretapping later in unbind?
So I'm wondering if there are other options? Although the directive here looks great in my opinion, but how to finish it in this case?

Answer the question

In order to leave comments, you need to log in

2 answer(s)
0
0xD34F, 2019-04-23
@Renhor

But how to remove this wiretapping later in unbind?

Well, obviously - for this you need to somehow save a link to the scroll handler.
The simplest option is to hook it to the element as a property:
bind(el, options) {
  const handler = () => { ... };
  window.addEventListener('scroll', handler);
  el.scrollHandler = handler;
},
unbind(el) {
  window.removeEventListener('scroll', el.scrollHandler);
},

Or you can use a Map to store the handlers - the keys will be the elements. It is only necessary not to forget in unbind, in addition to removing the handler, remove it from Map as well (or use WeakMap instead of Map).
In general, there is another way - since you connect handlers to window, in fact, one handler is enough for all elements at once: hang it when the page loads; again, there will be a Map, elements as keys, values ​​will be objects containing options.value, options.arg and what else will be needed there; in the handler bypass Map, hide/show elements:
const map = new Map();

window.addEventListener('scroll', function(e) {
  [...map.entries()].forEach(([ el, { className, deleteClass } ]) => {
    ...
  });
});

bind(el, options) {
  map.set(el, {
    className: options.value,
    deleteClass: options.arg === 'delete',
  });
},
unbind(el) {
  map.delete(el);
},

A
Alexander Aksentiev, 2019-04-23
@Sanasol

Change the variables yourself so that everything works as it should.
And so the usual removeEventListener, in order to remove it, you must pass the same callback as with addEventListener

let onscreen = {
    bind(el, options) {
        let className = options.value;
        let deleteClass = options.arg === "delete";

        window.addEventListener("scroll", this.action);
    },
    unbind() {
        window.removeEventListener("scroll", this.action);
    },
    action() {
        if (isVisible(el)) {
            if (deleteClass) {
                el.classList.remove(className)
            } else {
                el.classList.add(className)
            }
        } else {
            if (deleteClass) {
                el.classList.add(className)
            } else {
                el.classList.remove(className)
            }
        }
    }
};

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question