Search code examples
javascriptcssreactjswebantd

Returning the ID of the component which is in viewport


I want to return the value of the component currently in viewport.
To do this, I referd some documents, and thought I could make it by using Ant Design.

The function I refered to Ant Design is Anchor. I want to get key value of Anchor tag.
There is a document explaining usage of it, but don't know how to use it to get value of current component.

I made three components in my codes. And want to show their number at the top.
Can I know how to make it?

Sorry for difficult question to understand but I'm sure you could understand better if you see my code or CodeSandBox below. Thanks

code :

import styled from "styled-components";
import { Anchor } from "antd";
import { useState } from "react";

export default function App() {

  //tried to use these, but failed
  const [currentView, setCurrentView] = useState();
  console.log(currentView);
  window.addEventListener("scroll", () => {});

  return (
    <Wrap>
      <Anchor
        targetOffset={currentView}
        style={{ display: "none" }}
        items={[
          {
            key: "part-1",
            href: "#part-1",
            title: "Part 1"
          },
          {
            key: "part-2",
            href: "#part-2",
            title: "Part 2"
          },
          {
            key: "part-3",
            href: "#part-3",
            title: "Part 3"
          }
        ]}
      />
      <div className="fixed">currentcomponent : {currentView}</div>
      <div>{currentView}</div>
      <div id="part-1" className="compo1">
        components 1
      </div>
      <div id="part-2" className="compo2">
        components 2
      </div>
      <div id="part-3" className="compo3">
        components 3
      </div>
    </Wrap>
  );
}

const Wrap = styled.div`
  border: 3px solid black;
  width: 100vw;
  box-sizing: border-box;

  .fixed {
    position: sticky;
    top: 0;
  }

  .compo1 {
    border: 3px solid red;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100vw;
    height: 70vh;
  }

  .compo2 {
    border: 3px solid blue;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100vw;
    height: 70vh;
  }

  .compo3 {
    border: 3px solid green;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100vw;
    height: 70vh;
  }
`;

CodeSandBox: CodeSandBox


Solution

  • You can use Intersection Observer API, to observe the component in the viewport, you can add the observer in the useEffect hook without dependences to only run on the component initial load.

    import styled from "styled-components";
    import { Anchor } from "antd";
    import { useState, useEffect } from "react";
    
    export default function App() {
      const [currentView, setCurrentView] = useState();
      window.addEventListener("scroll", () => {});
    
      useEffect(() => {
        const components = document.querySelectorAll(".comp");
        const componentObserver = new IntersectionObserver(function (
          entries,
          observer
        ) {
          entries.forEach(function (entry) {
            if (entry.isIntersecting) {
              setCurrentView(entry.target.id);
            }
          });
        });
        components.forEach((comp) => {
          componentObserver.observe(comp);
        });
      }, []);
    
      return (
        <Wrap>
          <Anchor
            targetOffset={currentView}
            style={{ display: "none" }}
            items={[
              {
                key: "part-1",
                href: "#part-1",
                title: "Part 1"
              },
              {
                key: "part-2",
                href: "#part-2",
                title: "Part 2"
              },
              {
                key: "part-3",
                href: "#part-3",
                title: "Part 3"
              }
            ]}
          />
          <div className="fixed">currentcomponent : {currentView}</div>
          <div>{currentView}</div>
          <div id="part-1" className="compo1 comp">
            components 1
          </div>
          <div id="part-2" className="compo2 comp">
            components 2
          </div>
          <div id="part-3" className="compo3 comp">
            components 3
          </div>
        </Wrap>
      );
    }
    
    const Wrap = styled.div`
      border: 3px solid black;
      width: 100vw;
      box-sizing: border-box;
    
      .fixed {
        position: sticky;
        top: 0;
      }
    
      .compo1 {
        border: 3px solid red;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100vw;
        height: 100vh;
      }
    
      .compo2 {
        border: 3px solid blue;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100vw;
        height: 100vh;
      }
    
      .compo3 {
        border: 3px solid green;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100vw;
        height: 100vh;
      }
    `;
    
    

    Edit festive-varahamihira-ziemf5