如何使用 React 实现滚动间谍

IT技术 javascript reactjs scrollspy
2021-04-29 23:02:02

我想在没有引导程序的情况下实现滚动间谍

网上查了很多代码,都是用jQuery实现的

如何仅使用 React 的强大功能来实现scrollspy

4个回答

我制作了一个 React Wrapper(与Render-Props 一起使用):

实时 Codepen 示例


const {useState, useEffect, useCallback, useRef} = React;

/**
 *
 * @param {Object} scrollParent [DOM node of scrollable element]
 * @param {Array} _targetElements [Array of nodes to spy on]
 */
const spyScroll = (scrollParent, _targetElements) => {
  if (!scrollParent) return false;

  // create an Object with all children that has data-name attribute
  const targetElements =
    _targetElements ||
    [...scrollParent.children].reduce(
      (map, item) =>
        item.dataset.name ? { [item.dataset.name]: item, ...map } : map,
      {}
    );

  let bestMatch = {};

  for (const sectionName in targetElements) {
    if (Object.prototype.hasOwnProperty.call(targetElements, sectionName)) {
      const domElm = targetElements[sectionName];
      const delta = Math.abs(scrollParent.scrollTop - domElm.offsetTop); // check distance from top, takig scroll into account

      if (!bestMatch.sectionName) 
        bestMatch = { sectionName, delta };

      // check which delet is closest to "0"
      if (delta < bestMatch.delta) {
        bestMatch = { sectionName, delta };
      }
    }
  }

  // update state with best-fit section
  return bestMatch.sectionName;
};




/**
 * Given a parent element ref, this render-props function returns
 * which of the parent's sections is currently scrolled into view
 * @param {Object} sectionsWrapperRef [Scrollable parent node React ref Object]
 */
const CurrentScrolledSection = ({ sectionsWrapperRef, children }) => {
  const [currentSection, setCurrentSection] = useState();

  // adding the scroll event listener inside this component, and NOT the parent component, to prever re-rendering of the parent component when
  // the scroll listener is fired and the state is updated, which causes noticable lag.
  useEffect(() => {
    const wrapperElm = sectionsWrapperRef.current;
    if (wrapperElm) {
      wrapperElm.addEventListener('scroll', e => setCurrentSection(spyScroll(e.target)));
      setCurrentSection(spyScroll(wrapperElm));
    }

    // unbind
    return () => wrapperElm.removeEventListener('scroll', throttledOnScroll)
  }, []);

  return children({ currentSection });
};

function App(){
  const sectionsWrapperRef = useRef()
  return <CurrentScrolledSection sectionsWrapperRef={sectionsWrapperRef}>
    {({ currentSection }) => <div ref={sectionsWrapperRef}>
    <section 
      data-name="section-a" 
      className={currentSection === "section-a" ? 'active' : ''}
    >Section A</section>
    <section 
      data-name="section-b" 
      className={currentSection === "section-b" ? 'active' : ''}
    >Section B</section>
    <section 
      data-name="section-c" 
      className={currentSection === "section-c" ? 'active' : ''}
    >Section C</section>
    </div>
  }
  </CurrentScrolledSection>
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
)
html, body, #root{ height: 95%; overflow:hidden; }

#root > div{ 
  padding-bottom:20em; 
  height: 100%; 
  overflow:auto; 
  box-sizing: border-box;
}

section{ 
  height: 50vh;
  border-bottom: 1px solid red;
}

section.active{ background:lightyellow; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.0/umd/react-dom.production.min.js"></script>
<div id='root'></div>

它并不完美,因为滚动方向确实很重要,因为它暗示了用户的意图。

现场运行演示

https://codesandbox.io/s/gallant-wing-ue2ks

脚步

1.添加依赖

使用 npm

npm i react-scrollspy

或使用纱线

yarn add react-scrollspy

2. 创建您的 HTML 部分

<section id="section-1">
    <!-- Content goes here -->
</section>
<section id="section-2">
    <!-- Content goes here -->
</section>

<!-- .... -->

<section id="section-n">
    <!-- Content goes here -->
</section>

3. 将这些部分的 ID 添加到 Scrollspy items

<Scrollspy items={ ['section-1', 'section-2', ..., 'section-n'] } />

4.添加一些样式

检查此文档https://makotot.github.io/react-scrollspy/#section-3

看看react-ui-scrollspy它是非常容易使用的。

完全披露:我维护这个包。

  1. 在您的导航组件中
<div>
  <p data-to-scrollspy-id="first">Section 1</p>
  <p data-to-scrollspy-id="second">Section 2</p>
</div>
  1. 将要监视的元素包裹在<ScrollSpy>组件中。
import ScrollSpy from "react-ui-scrollspy";

<ScrollSpy>
  <div id="first">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut dolores
    veritatis doloremque fugit. Soluta aperiam atque inventore deleniti,
    voluptatibus non fuga eos magni natus vel, rerum excepturi expedita.
    Tempore, vero!
  </div>
  <div id="second">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut dolores
    veritatis doloremque fugit. Soluta aperiam atque inventore deleniti,
    voluptatibus non fuga eos magni natus vel, rerum excepturi expedita.
    Tempore, vero!
  </div>
</ScrollSpy>
  1. 为当您的导航元素处于活动状态时编写样式 index.css
.active-scroll-spy {
  background-color: yellowgreen;
  border-radius: 15px;
}

您可以使用React-scrollspy. 访问这个 git repo React-scrollspy