Определить направление прокрутки в React js - PullRequest
1 голос
/ 21 июня 2020

Я пытаюсь определить, находится ли событие прокрутки вверх или вниз, но не могу найти решение.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

const Navbar = ({ className }) => {
  const [y, setY] = useState(0);

  const handleNavigation = (e) => {
    const window = e.currentTarget;
    if (y > window.scrollY) {
      console.log("scrolling up");
    } else if (y < window.scrollY) {
      console.log("scrolling down");
    }
    setY(window.scrollY);
  };

  useEffect(() => {
    setY(window.scrollY);

    window.addEventListener("scroll", (e) => handleNavigation(e));
  }, []);

  return (
    <nav className={className}>
      <p>
        <i className="fas fa-pizza-slice"></i>Food finder
      </p>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
    </nav>
  );
};

export default Navbar;

Обычно оно всегда определяется как «вниз», потому что y в handleNavigation всегда 0. Если я проверяю состояние в DevTool, состояние y обновляется, а в handleNavigation - нет.

Есть предложения, что я делаю не так?

Спасибо за ваша помощь

1 Ответ

3 голосов
/ 21 июня 2020

Это потому, что вы определили useEffect() без каких-либо зависимостей , поэтому ваш useEffect() будет запускаться только один раз и никогда не вызывает handleNavigation() при y изменениях . Чтобы исправить это, вам нужно добавить y в свой массив зависимостей, чтобы ваш useEffect() запускался после изменения значения y. Затем вам нужно еще одно изменение, чтобы вступить в силу в вашем коде, где вы пытаетесь инициализировать свой y с помощью window.scrollY, поэтому вы должны либо сделать это в своем useState(), например:

const [y, setY] = useState(window.scrollY);

useEffect(() => {
  window.addEventListener("scroll", (e) => handleNavigation(e));

  return () => { // return a cleanup function to unregister our function since its gonna run multiple times
    window.removeEventListener("scroll", (e) => handleNavigation(e));
  };
}, [y]);

Если по какой-то причине окно там может быть недоступно или вы не хотите делать это здесь, вы можете сделать это двумя отдельными useEffect() s.

Итак, ваши useEffect() s должны быть такими:

useEffect(() => {
  setY(window.scrollY);
}, []);

useEffect(() => {
  window.addEventListener("scroll", (e) => handleNavigation(e));

  return () => { // return a cleanup function to unregister our function since its gonna run multiple times
    window.removeEventListener("scroll", (e) => handleNavigation(e));
  };
}, [y]);

ОБНОВЛЕНИЕ

После самостоятельной реализации этого решения. Я обнаружил, что к этому решению следует применить некоторые примечания. Итак, , поскольку handleNavigation() изменит значение y напрямую, мы можем игнорировать y как нашу зависимость, а затем добавить handleNavigation() как зависимость к нашему useEffect(), тогда из-за этого изменения мы должен оптимизировать handleNavigation(), поэтому мы должны использовать для него useCallback(). Тогда окончательный результат будет примерно таким:

const handleNavigation = useCallback(
  e => {
    const window = e.currentTarget;
    if (y > window.scrollY) {
      console.log("scrolling up");
    } else if (y < window.scrollY) {
      console.log("scrolling down");
    }
    setY(window.scrollY);
  }, [y]
);

useEffect(() => {
  setY(window.scrollY);
  window.addEventListener("scroll", e => handleNavigation(e));

  return () => {
    window.removeEventListener("scroll", e => handleNavigation(e));
  };
}, [handleNavigation]);

Я тестирую это снова, но все еще сталкиваюсь с той же проблемой , что и раньше, и при попытке прокрутки вверх он сломается. и будет утешать некоторую прокрутку вниз в событии прокрутки вверх.

Итак, я пришел к выводу, что мемоизация в этом случае нам совсем не поможет. Наконец, я обнаружил, что подход, который мы здесь используем, не очень хорошо работает, потому что нам следует подумать о более эффективном способе сохранения последней позиции прокрутки страницы каждый раз, когда мы хотим проверить новую позицию. Кроме того, чтобы избавиться от огромного количества утешительных прокрутки вверх и прокрутки вниз , мы должны определить порог для запуска изменения события прокрутки. Так что я просто немного поискал в Интернете и получил это gist , которое было очень полезно. Затем, вдохновившись этим, я реализую более простую версию.

Вот как это выглядит:

const [scrollDir, setScrollDir] = useState("scrolling down");

useEffect(() => {
  const threshold = 0;
  let lastScrollY = window.pageYOffset;
  let ticking = false;

  const updateScrollDir = () => {
    const scrollY = window.pageYOffset;

    if (Math.abs(scrollY - lastScrollY) < threshold) {
      ticking = false;
      return;
    }
    setScrollDir(scrollY > lastScrollY ? "scrolling down" : "scrolling up");
    lastScrollY = scrollY > 0 ? scrollY : 0;
    ticking = false;
  };

  const onScroll = () => {
    if (!ticking) {
      window.requestAnimationFrame(updateScrollDir);
      ticking = true;
    }
  };

  window.addEventListener("scroll", onScroll);

  return () => window.removeEventListener("scroll", onScroll);
}, []);

Как это работает?

Я просто go сверху вниз и объясните каждый блок кода.

  • Итак, я просто определил пороговую точку с начальным значением 0, а затем всякий раз, когда прокрутка идет вверх или вниз, она будет новый расчет, вы можете увеличить его, если не хотите сразу рассчитывать новое смещение страницы.

  • Затем вместо scrollY я решаю использовать pageYOffset, что более надежно в пересечении просмотр.

  • В функции updateScrollDir мы просто проверим, соблюдается ли порог, затем, если он соблюден, я укажу базу направления прокрутки текущей и предыдущей страницы смещение.

  • Наиболее важной его частью является функция onScroll. Мы просто использовали requestAnimationFrame, чтобы убедиться, что мы вычисляем новое смещение после того, как страница была полностью отрисована после прокрутки. А затем с флагом ticking мы убедимся, что мы просто запускаем обратный вызов нашего прослушивателя событий один раз в каждом requestAnimationFrame.

  • Наконец, мы определили наш слушатель и нашу функцию очистки .

  • Тогда состояние scrollDir будет содержать фактическое направление прокрутки.

Рабочая демонстрация:

CodeSandbox

...