scroll menu navigation

악음·2023년 3월 22일
0

기능구현

목록 보기
4/4
import { memoize } from 'lodash';
import React, {
  RefObject,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
} from 'react';
import useStores from 'stores/useStores';
import NavigationMenu from './NavigationMenu';
import css from './ScrollMenuNavigation.module.scss';
import Section from './Section';

export type curationInterests = {
  width: number;
  height: number;
  url: string;
  text: string[];
  n: number;
};

export interface CurationsInterface {
  id: number;
  title: string;
  description: string;
  curationLocation: string;
  curationType: string;
  itemType: 'BANNER' | 'INTEREST' | 'PRODUCT';
  offsetName: string;
  viewType: 'VERTICAL' | 'HORIZONTAL';
  curationProducts: any[];
  curationBanners: {
    url: string;
    width: number;
    height: number;
    link: {
      web: string;
      aos: string;
      ios: string;
    };
  }[];
  curationReviews: any[];
  curationInterests: curationInterests[];
}

export interface ItemListInterface extends CurationsInterface {
  verticalRef: React.RefObject<HTMLDivElement>;
  horizonRef: React.RefObject<HTMLDivElement>;
}

export type horizontalListType = {
  curationId: number;
  text: string;
  horizonRef: RefObject<HTMLDivElement>;
};

export type verticalListType = Omit<ItemListInterface, 'horizonRef'>;

interface ScrollMenuNavigationProps {
  gapTop: number;
  top: string;
  curations: CurationsInterface[];
  offsets: {
    curationId: number;
    text: string;
  }[];
}

export interface SliderRef extends HTMLDivElement {
  sliderCurrentPage: number;
  setSliderCurrentPage: React.Dispatch<React.SetStateAction<number>>;
}

const ScrollMenuNavigation = ({
  gapTop = 0,
  top = '6%',
  curations,
  offsets,
}: ScrollMenuNavigationProps) => {
  const [selectedId, setSelectedId] = useState(0);
  const [isClickNavMenu, setIsClickNavMenu] = useState(false);
  const [marginBottom, setMarginBottom] = useState(0);
  // const [targetScrollTop, setTartgetScrollTop] = useState(0);
  const { layout } = useStores();
  const sliderRef = useRef<SliderRef>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const getVerticalListHandler = memoize((curations) => {
    return curations.map((item: any) => ({
      ...item,
      verticalRef: useRef<HTMLDivElement>(null),
    }));
  });
  const verticalList: verticalListType[] = getVerticalListHandler(curations);

  const verticlaRefListHandler = memoize((verticalList: verticalListType[]) => {
    return verticalList.map((item) => item.verticalRef);
  });
  const verticalRefList = verticlaRefListHandler(verticalList);

  const horizontalList: horizontalListType[] = offsets.map((item) => ({
    ...item,
    horizonRef: useRef<HTMLDivElement>(null),
  }));

  const findMarginBottom = () => {
    const lastVerticalRef =
      verticalList[verticalList.length - 1]?.verticalRef?.current;
    const containerRefCurrent = containerRef.current;

    if (
      lastVerticalRef &&
      containerRefCurrent &&
      offsets.length > 0 &&
      lastVerticalRef?.offsetHeight > 0
    ) {
      if (lastVerticalRef.offsetHeight < window.innerHeight) {
        const computeMarginBottom =
          window.innerHeight - lastVerticalRef.offsetHeight - gapTop - 110;
        setMarginBottom(computeMarginBottom);
      }
    }
  };

  const naviMenuClickHandler = (curationId: number) => {
    const id = Number(curationId);
    setSelectedId(id);
    setIsClickNavMenu(true);
    const getVercitalObject = verticalRefList.find((item) => {
      return Number(item.current?.id) === id;
    });
    if (getVercitalObject?.current) {
      window.scrollTo({
        top: getVercitalObject?.current.offsetTop - gapTop,
        behavior: 'smooth',
      });
      // setTartgetScrollTop(getVercitalObject?.current.offsetTop);
    }
    const horizontalMenuCurrent = horizontalList.find(
      (item) => item.curationId === id
    )?.horizonRef.current;
    const sliderCurrent = sliderRef.current;

    if (horizontalMenuCurrent && sliderCurrent) {
      const offsetLeft = horizontalMenuCurrent.offsetLeft;
      const offsetWeight = horizontalMenuCurrent.clientWidth * 0.5;
      const sliderWidthWeight = sliderCurrent.offsetWidth * 0.5;
      const targetLeft = offsetLeft + offsetWeight - sliderWidthWeight;

      if (targetLeft !== sliderCurrent.scrollLeft) {
        sliderCurrent.scrollTo({
          left: targetLeft,
          behavior: 'smooth',
        });
      }
    }
  };

  useEffect(() => {
    const scrollHandler = () => {
      //화면에 스크롤이 없을때
      if (
        window.scrollY === 0 &&
        window.scrollY + window.innerHeight ===
          document.scrollingElement?.scrollHeight
      ) {
        const endId = horizontalList[horizontalList.length - 1]?.curationId;
        const findVerticalItem = verticalList.find((item) => item.id === endId);
        if (findVerticalItem) {
          return setSelectedId(Number(findVerticalItem.id));
        } else {
          if (!isClickNavMenu) {
            return setSelectedId(0);
          }
        }
      }

      // 처음시작일때
      if (window.scrollY === 0) {
        if (!isClickNavMenu) {
          setSelectedId(0);
          if (sliderRef?.current?.setSliderCurrentPage) {
            sliderRef?.current?.setSliderCurrentPage(0);
          }
          return sliderRef.current?.scrollTo({ left: 0 });
        }
      }
      // 스크롤이 마지막에 닿았을때
      // if (
      //   window.scrollY + window.innerHeight ===
      //   document.scrollingElement?.scrollHeight
      // ) {
      //   const endId = horizontalList[horizontalList.length - 1]?.curationId;
      //   const findVerticalItem = verticalList.find((item) => item.id === endId);
      //   if (findVerticalItem) {
      //     if (!isClickNavMenu) {
      //       return setSelectedId(Number(findVerticalItem.id));
      //     }
      //   } else {
      //     if (!isClickNavMenu) {
      //       return setSelectedId(0);
      //     }
      //   }
      // }

      if (verticalList[0].verticalRef.current) {
        verticalList.forEach((section) => {
          if (section.verticalRef.current && sliderRef.current) {
            const sectionCurrent = section.verticalRef.current;
            const sectionAreaStart = sectionCurrent.offsetTop;
            const sctionAreaEnd =
              sectionCurrent.offsetTop + sectionCurrent.offsetHeight;
            // 색션이 window에 최상단위치+gapTop에 위치에 걸릴경우를 분기한다.
            if (
              sectionAreaStart <= window.scrollY + gapTop &&
              window.scrollY + gapTop <= sctionAreaEnd
            ) {
              if (!isClickNavMenu) {
                if (Number(section.id) !== selectedId) {
                  setSelectedId(Number(section.id));
                }
              }
            }
          }
        });
      }
    };

    scrollHandler();
    window.addEventListener('scroll', scrollHandler);
    return () => {
      window.removeEventListener('scroll', scrollHandler);
    };
  }, [verticalList, isClickNavMenu]);

  useEffect(() => {
    // const targetTopHandler = () => {
    //   if (targetScrollTop > 0) {
    //     const TargetReach = ((window.scrollY + gapTop) / targetScrollTop) * 100;
    //     const targetPer = 100;
    //     if (TargetReach === targetPer) {
    //       setIsClickNavMenu(false);
    //       setTartgetScrollTop(0);
    //     }
    //   }
    // };

    const mouseWheelEvent = () => {
      if (isClickNavMenu) {
        setIsClickNavMenu(false);
      }
    };

    window.addEventListener('touchmove', mouseWheelEvent);
    window.addEventListener('wheel', mouseWheelEvent);
    // window.addEventListener('scroll', targetTopHandler);
    return () => {
      window.removeEventListener('touchmove', mouseWheelEvent);
      window.removeEventListener('wheel', mouseWheelEvent);
      // window.removeEventListener('scroll', targetTopHandler);
    };
  }, [isClickNavMenu]);

  useLayoutEffect(() => {
    const selectedIdEvent = () => {
      if (selectedId !== 0) {
        // 오프셋 메뉴 가운대로 몰기
        const horizontalMenuCurrent = horizontalList.find(
          (item) => item.curationId === selectedId
        )?.horizonRef.current;
        const sliderCurrent = sliderRef.current;

        if (horizontalMenuCurrent && sliderCurrent) {
          const offsetLeft = horizontalMenuCurrent.offsetLeft;
          const offsetWeight = horizontalMenuCurrent.clientWidth * 0.5;
          const sliderWidthWeight = sliderCurrent.offsetWidth * 0.5;
          const targetLeft = offsetLeft + offsetWeight - sliderWidthWeight;

          if (targetLeft !== sliderCurrent.scrollLeft && !isClickNavMenu) {
            sliderCurrent.scrollTo({
              left: targetLeft,
            });
          }

          // 슬라이더에 넘겨줄 페이지값
          if (sliderRef?.current?.setSliderCurrentPage) {
            const totalPage = Math.floor(
              sliderCurrent.scrollWidth / sliderCurrent.offsetWidth
            );
            const pagePerWidth = [];
            for (let i = 0; i < totalPage; i++) {
              pagePerWidth.push((i + 1) * sliderCurrent.offsetWidth);
            }
            let currentPageIndex = pagePerWidth.findIndex(
              (item) => item >= horizontalMenuCurrent.offsetLeft
            );
            if (
              sliderCurrent.scrollWidth <=
              horizontalMenuCurrent.offsetLeft +
                horizontalMenuCurrent.clientWidth
            ) {
              currentPageIndex = totalPage;
            }
            sliderRef.current?.setSliderCurrentPage(currentPageIndex);
          }
        }
      }
    };
    selectedIdEvent();
  }, [selectedId, isClickNavMenu]);

  useLayoutEffect(() => {
    // findMarginBottom();
    layout.seenCurationScrollTop();
    layout.isGoToScrollToTopHandler(false);
  }, []);

  return (
    <div ref={containerRef} className={css['ScrollMenuNavigationWrap']}>
      {offsets.length > 0 ? (
        <NavigationMenu
          horizontalList={horizontalList}
          selectedId={selectedId}
          naviMenuClickHandler={naviMenuClickHandler}
          sliderRef={sliderRef}
          top={top}
        />
      ) : null}
      <div style={{ marginBottom: marginBottom }}>
        {verticalList.length > 0
          ? verticalList.map((data, index) => {
              return (
                <Section
                  index={index}
                  key={data.id}
                  ref={data.verticalRef}
                  data={data}
                />
              );
            })
          : null}
      </div>
    </div>
  );
};

export default ScrollMenuNavigation;

import React, { forwardRef } from 'react';
import HorizontalItems from 'template/Home/MainCuration/HorizontalItems/HorizontalItems';
import VerticalItems from 'template/Home/MainCuration/VerticalItems/VerticalItems';
import { verticalListType } from '..';
import BannerSection from './BannerSection';
import Interests from './Interests/Interests';
import css from './Section.module.scss';

interface SectionProps {
  data: verticalListType;
  index: number;
}

const Section = forwardRef<HTMLDivElement, SectionProps>(
  ({ data, index }, ref) => {
    const { id, itemType, curationBanners, curationInterests, viewType } = data;

    return (
      <div className={css['sectionWrap']} id={String(id)} ref={ref}>
        {itemType === 'BANNER' && curationBanners && (
          <BannerSection index={index} curationBanners={curationBanners} />
        )}
        {itemType === 'PRODUCT' && viewType === 'VERTICAL' && (
          <div style={{ marginBottom: 50 }}>
            <VerticalItems deals={data} />
          </div>
        )}
        {itemType === 'PRODUCT' && viewType === 'HORIZONTAL' && (
          <div style={{ marginBottom: 50 }}>
            <HorizontalItems deals={data} />
          </div>
        )}
        {itemType === 'INTEREST' && (
          <Interests curationInterests={curationInterests} />
        )}
      </div>
    );
  }
);

export default React.memo(Section);

profile
RN/react.js개발자이며 배운것들을 제가 보기위해서 정리하기 때문에 비속어 오타가 있을수있습니다.

0개의 댓글