Scrollprogress(가로 스크롤) 만들기

성준영·2022년 8월 25일
0

스토리북

목록 보기
3/3
post-thumbnail

scroll-bar는 어느 웹 사이트를 방문하더라도 쉽게 볼 수 있는 UI이다. 그런데 웹 사이트를 만들 때 스크롤이 레이아웃을 종종 망가뜨릴 때가 있었기에 이번에 Scrollprogress-bar를 만들어 보려 한다.

스크롤 만들기

  • 트리 구조
|-- components
|   `-- ScrollProgress
|       |-- ScrollProgress.stories.tsx
|       |-- ScrollProgress.styles.ts
|       |-- ScrollProgress.tsx
|       `-- ScrollProgress.types.ts
|-- libs
|   |-- hooks
|   |   `-- useScrollProgress.ts
|   `-- utils
|       `-- moveScrollProgress.ts
|-- App.tsx
|-- main.tsx
`-- vite-env.d.ts

useScrollProgress.ts

현재 viewpoint스크롤바의 몇 퍼센트 위치에 있는지 계산하는 커스텀 훅이다.

import { useEffect, useState } from "react";

const useScrollProgress = () => {
  const [scroll, setScroll] = useState(0);

  const handleProgress = () => {
    const totalScroll = document.documentElement.scrollTop;
    const windowHeight = window.innerHeight;
    const scroll = (totalScroll / windowHeight) * 100;
    setScroll(scroll);
  };

  useEffect(() => {
    window.addEventListener("scroll", handleProgress);
    return () => {
      window.removeEventListener("scroll", handleProgress);
    };
  }, [handleProgress]);
  return scroll;
};
export default useScrollProgress;

moveScrollProgress.ts

클릭된 스크롤바의 지점에 맞게 viewpoint를 옮기는 함수

/**
 *
 * @param clientX - x 좌표
 * @param scrollWidth - ScrollProgress의 최대 길이
 */

const moveScrollProgress = (clientX: number, scrollWidth: number) => {
  window.scrollTo({
    top: window.innerHeight * (clientX / scrollWidth),
    behavior: "smooth",
  });
};

export default moveScrollProgress;

ScrollProgress.types.ts

import { DetailedHTMLProps, HTMLAttributes } from "react";

export interface ScrollProgressState
  extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  colors: string | string[];
  progress: number;
}

ScrollProgress.tsx

import useScrollProgress from "@/libs/hooks/useScrollProgress";
import moveScrollProgress from "@/libs/utils/moveScrollProgress";
import { useRef } from "react";
import * as S from "./ScrollProgress.styles";
import { ScrollProgressState } from "./ScrollProgress.types";

/**
 * @param colors - ScrollProgress의 색상 변경
 **  string형식으로 입력 시 ex) colors="blue" => background :"blue"
 **  string[]형식으로 입력 시 ex) colors=["blue","yellow"] => background : linear-gradient(to left, "blue", "yellow")
 *
 *
 */

const ScrollProgress = ({
  colors,
  ...rest
}: Omit<ScrollProgressState, "progress">) => {
  const scroll = useScrollProgress();
  const progressRef = useRef<HTMLDivElement>(null);

  const onClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    const { clientX } = event;
    if (progressRef && progressRef.current) {
      const { scrollWidth } = progressRef.current;
      moveScrollProgress(clientX, scrollWidth);
    }
  };

  return (
    <S.Container {...rest} onClick={onClick} ref={progressRef}>
      <S.ProgressBar colors={colors} progress={scroll} />
    </S.Container>
  );
};

export default ScrollProgress;

import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { ScrollProgressState } from "./ScrollProgress.types";

export const Container = styled.div`
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: #ccc;
`;

export const ProgressBar = styled.div<ScrollProgressState>`
  ${({ colors }) =>
    Array.isArray(colors)
      ? css`
          background: linear-gradient(to left, ${colors.join(",")});
        `
      : css`
          background: ${colors};
        `}
  height: 0.5rem;
  width: ${({ progress }) => progress + "%"};
`;

ScrollProgress.stories.tsx

import ScrollProgress from "./ScrollProgress";
import { ComponentStory, ComponentMeta } from "@storybook/react";

export default {
  title: "ScrollProgress",
  components: ScrollProgress,
} as ComponentMeta<typeof ScrollProgress>;

const Template: ComponentStory<typeof ScrollProgress> = (args) => (
  <div style={{ height: "200vh" }}>
    <ScrollProgress {...args} />
  </div>
);

export const ProgressArray = Template.bind({});
ProgressArray.args = {
  colors: ["rgb(255, 166, 166)", "rgb(126, 197, 255)"],
};

export const ProgressString = Template.bind({});
ProgressString.args = {
  colors: "blue",
};

Storybook 실행 화면

코드

링크

profile
기록해버리기

0개의 댓글