Muscardinus

Scroll Spy 본문

FrontEnd/Basic Skills

Scroll Spy

Muscardinus 2022. 1. 31. 00:41
728x90

Vanilla JS

<div id="app">
      <ul id="nav">
        <li class="on"><button>1</button></li>
        <li><button>2</button></li>
        <li><button>3</button></li>
        <li><button>4</button></li>
        <li><button>5</button></li>
        <li><button>6</button></li>
        <li><button>7</button></li>
        <li><button>8</button></li>
      </ul>
      <div id="contents">
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
        <div>6</div>
        <div>7</div>
        <div>8</div>
      </div>
    </div>
const navElem = document.querySelector("#nav");
const navItems = Array.from(navElem.children);
const contentsElem = document.querySelector("#contents");
const contentItems = Array.from(contentsElem.children);

const scrollSpyObserver = new IntersectionObserver(
  entries => {
    // 최초에는 observe가 다 되지만
    // 추후에는 intersect가 되는 el, 이제 안 되는 el 두개만 나온다.
    const { target } = entries.find(entry => entry.isIntersecting) || {};
    const index = contentItems.indexOf(target);
    Array.from(navElem.children).forEach((c, i) => {
      if (i === index) c.classList.add("on");
      else c.classList.remove("on");
    });
  },
  {
    root: null,
    rootMargin: "0px",
    threshold: 0.5
    // 50%만 보여도 true
  }
);
contentItems.forEach(item => scrollSpyObserver.observe(item));

navElem.addEventListener("click", e => {
  const targetElem = e.target;
  if (targetElem.tagName === "BUTTON") {
    const targetIndex = navItems.indexOf(targetElem.parentElement);
    contentItems[targetIndex].scrollIntoView({
      block: "start",
      behavior: "smooth"
    });
  }
});

React

import React, { useState, useRef, useEffect } from "react";
import Nav from "./Nav";
import Content from "./Content";
import "./style.css";

const pages = Array.from({ length: 8 }).map((_, i) => i + 1);

const App = () => {
  const [viewIndex, setViewIndex] = useState(0);
  const contentRef = useRef([]);
  const moveToPage = index => () => {
    contentRef.current[index].scrollIntoView({
      block: "start",
      behavior: "smooth"
    });
  };

  const scrollSpyObserver = new IntersectionObserver(
    entries => {
      const { target } = entries.find((entry) => entry.isIntersecting) || {};
      const index = contentRef.current.indexOf(target);
      setViewIndex(index);
    },
    {
      root: null,
      rootMargin: "0px",
      threshold: 0.5
    }
  );

  useEffect(() => {
    contentRef.current.forEach((item) => scrollSpyObserver.observe(item));
    return () => {
      contentRef.current.forEach((item) => scrollSpyObserver.unobserve(item));
    };
  }, []);

  return (
    <div id="app">
      <Nav pages={pages} viewIndex={viewIndex} moveToPage={moveToPage} />
      <div id="contents">
        {pages.map((p, i) => (
          <Content key={p} ref={(r) => (contentRef.current[i] = r)} page={p} />
        ))}
      </div>
    </div>
  );
};

export default App;
import React, { forwardRef } from "react";

const Content = ({ page }, ref) => <div ref={ref}>{page}</div>;

export default forwardRef(Content);
728x90

'FrontEnd > Basic Skills' 카테고리의 다른 글

Dropdown Menu  (0) 2022.03.13
Auto Complete  (0) 2022.03.13
Search bar  (0) 2022.02.09
Scroll Top  (0) 2022.01.31
Infinite Scroll  (0) 2022.01.31
Comments