문제점
스크롤이 끝나면 useInterval을 중단 시키는 코드를 useInterval의 콜백함수 안에 작성해줬다.
문제점은 delay초마다 스크롤이 끝난지 확인하는 로직이 실행되어 비효율적이다.
// WaffleCardList.tsx
useInterval(
() => {
if (!(targetDom instanceof Element)) return;
const { scrollLeft, clientWidth } = targetDom;
const currentScrolledWidth = Math.ceil(scrollLeft + clientWidth);
const isRenderedCards = currentScrolledWidth > targetDom.clientWidth;
const isFinishScroll = currentScrolledWidth === targetDom.scrollWidth;
isRenderedCards && isFinishScroll && setIsPlayMove(false);
targetDom.scrollLeft += 1;
},
15,
isPlayMove,
);
해결책
intersection observer를 도입하여, 마지막 카드를 감시 target으로 걸어준다.
마지막 카드가 전부 보여질 때 useInterval을 중단시키는 콜백함수를 실행시키는 방법이다.
하지만 마지막 카드에 ref를 걸어주려면 forwardRef를 사용해야하는데 코드가 복잡해질 것 같아서
카드리스트에 새로운 div태그를 추가해줬다.
tab이 변경될 때마다 카드리스트와 div가 다시 렌더링되기 때문에
callback ref를 이용해 div가 새로 생성 될 때마다 Element가 target에 자동으로 할당되도록 했다.
하지만 새로 생성되는건 가상돔이고, 변경된 부분이 없기 때문에 돔은 새로 생성되지 않았다.
그래서 content로 type을 주어 tab이 바뀔 때마다 변경 부분이 생기도록 했다.
// useScrollAnimation.ts
const [target, setTarget] = useState<HTMLDivElement | null>(null);
const onIntersect: IntersectionObserverCallback = ([entry]) => {
if (entry.isIntersecting) {
setIsPlayMove(false);
}
};
useEffect(() => {
let observer: IntersectionObserver;
if (target) {
observer = new IntersectionObserver(onIntersect, {
threshold: 0.1,
});
observer.observe(target);
}
return () => observer && observer.disconnect();
}, [target]);
// WaffleCardList.tsx
...
const [
isPlayMove,
setIsPlayMove,
moveScrollToFront,
moveScrollToBack,
setTarget,
] = useScrollAnimation(cardsListRef.current, [type]);
return (
<>
{waffleCards?.map(waffleCard => (
<StyledWaffleCard
type={type === 'my' ? 'my' : 'basic'}
key={waffleCard.id}
waffleCardData={waffleCard}
onClickWaffleCard={handleClickWaffleCard}
onClickLikeToggle={handleClickLikeToggle}
onClickEdit={handleClickWaffleCardEdit}
onClickDelete={handleClickWaffleCardDelete}
/>
))}
<div ref={setTarget}>{type}</div> // 여기
</>
);