@Renhor

Vue. Как лучше реализовать функцию видимости элемента на экране?

Доброго времени суток!

Есть страница HomePage.vue, на ней несколько DOM-элементов, которые должны отображаться, когда появляются в области видимости экрана.

Есть простая функция проверки видимости
function isVisible(el) {
  let elTop = el.offsetTop;
  let rect = el.getBoundingClientRect();

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


Хотел сначала создать кастомную директиву v-onscreen:delete="'translatedRight'"

Агрумент :delete - если нужно удалить класс
Значение translatedRight - класс, который удаляем / добавляем

Как бы и нет проблем создать в директиве в хуке bind
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() {
    // ???
  }
};


Но как снять эту прослушку потом в unbind?

Вот и думаю, может есть другие варианты? Хотя директива тут смотрится замечательно на мой взгляд, только как ее допилить в таком случае?
  • Вопрос задан
  • 1261 просмотр
Решения вопроса 1
0xD34F
@0xD34F Куратор тега Vue.js
Но как снять эту прослушку потом в unbind?

Ну, очевидно же - для этого надо как-то сохранить ссылку на обработчик скролла.

Самый простой вариант - цеплять его к элементу в качестве свойства:

bind(el, options) {
  const handler = () => { ... };
  window.addEventListener('scroll', handler);
  el.scrollHandler = handler;
},
unbind(el) {
  window.removeEventListener('scroll', el.scrollHandler);
},

Или можно использовать Map для хранения обработчиков - ключами будут элементы. Надо только не забыть в unbind в дополнение к снятию обработчика удалять его так же и из Map'а (ну или вместо Map используйте WeakMap).

А вообще, есть и другой путь - так как обработчики вы подключаете к window, на самом деле достаточно одного обработчика сразу для всех элементов: вешаете его при загрузке страницы; опять же, будет Map, элементы в качестве ключей, значениями будут объекты, содержащие в себе options.value, options.arg и что там ещё будет нужно; в обработчике обходите Map, скрываете/показываете элементы:

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);
},
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Sanasol
@Sanasol
нельзя просто так взять и загуглить ошибку
Переменные сами уж разберите чтоб все работало как надо.
А так обычный removeEventListener, чтобы его удалить надо передать тот же колбек что и при 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)
            }
        }
    }
};
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы