회사 plugin 작업중 슬라이드 모션을 구현하는 작업을 하게 되었는데
상단에 카테고리 컴포넌트가 있고 각 카테고리를 클릭하면 양 옆 슬라이드 모션으로 컨텐츠가 나오는데 만약 현재 카테고리에서 오른쪽의 카테고리를 누르면 화면이 오른쪽으로 한번만 움직이면서 바로 누른 카테고리 컨텐츠가 나오도록 해야했다.
이는 Slick을 활용하기에는 조금 번거로울 듯 싶어 직접 구현해 보기로 하였다.
구현 연습 영상
SlideContainer.js
카테고리와 컴포넌트와 슬라이드 될 컨텐츠 컴포넌트 2개로 구성하였다.
const SlideContainer = () => {
const [currIdx, setCurrIdx] = useState(null);
const [selectedCategory , setSelectedCategory] = useState(null);
const onClickCategory = (list, idx) => {
setCurrIdx(idx);
setSelectedCategory(list);
}
return (
<div className="Root">
<Category onClickCategory={onClickCategory}/>
<Slide currIdx={currIdx} selectedCategory={selectedCategory}/>
</div>
);
};
export default SlideContainer;
Slide.js
import React, { useEffect, useRef, useState } from 'react';
import { usePrevious } from '../../hooks/common';
import "./Slide.scss";
const Slide = ({currIdx, selectedCategory}) => {
const firstRef = useRef();
const secondRef = useRef();
const thirdRef = useRef();
const [firstChildren, setFirstChildren] = useState(null);
const [secondChildren, setSecondChildren] = useState(null);
const [thirdChildren, setThirdChildren] = useState(null);
const prevIdx = usePrevious(currIdx)
const prevCategory = usePrevious(selectedCategory)
useEffect(() => {
const firstListEl = firstRef.current;
const secondListEl = secondRef.current;
const thirdListEl = thirdRef.current;
if(prevIdx < currIdx) {
// moveRight
const previousEl = (<img src={prevImg} alt="img"/>)
const selectedEl = (<img src={selectedImg} alt="img"/>)
const center = document.getElementsByClassName('currentProduct')[0];
const next = document.getElementsByClassName('nextProduct')[0];
const previous = document.getElementsByClassName('previousProduct')[0];
center.classList.remove('currentProduct');
next.classList.remove('nextProduct');
previous.classList.remove('previousProduct');
center.classList.add('previousProduct');
next.classList.add('currentProduct');
previous.classList.add('nextProduct');
center.style.transition = "transform 1s";
next.style.transition = "transform 1s";
previous.style.transition = "transform 1s";
previous.style.transition = 'none'
if (firstListEl.className.includes('previousProduct')) {
setFirstChildren(previousEl);
} else if (secondListEl.className.includes('previousProduct')) {
setSecondChildren(previousEl);
} else if (thirdListEl.className.includes('previousProduct')) {
setThirdChildren(previousEl);
}
if (firstListEl.className.includes('currentProduct')) {
setFirstChildren(selectedEl);
} else if (secondListEl.className.includes('currentProduct')) {
setSecondChildren(selectedEl);
} else if (thirdListEl.className.includes('currentProduct')) {
setThirdChildren(selectedEl);
}
} else if(prevIdx > currIdx) {
// moveLeft
const previousEl = (<img src={prevImg} alt="img"/>)
const selectedEl = (<img src={selectedImg} alt="img"/>)
const center = document.getElementsByClassName('currentProduct')[0];
const next = document.getElementsByClassName('nextProduct')[0];
const previous = document.getElementsByClassName('previousProduct')[0];
center.classList.remove('currentProduct');
next.classList.remove('nextProduct');
previous.classList.remove('previousProduct');
center.classList.add('nextProduct');
next.classList.add('previousProduct');
previous.classList.add('currentProduct');
center.style.transition = "transform 1s";
next.style.transition = "transform 1s";
previous.style.transition = "transform 1s";
next.style.transition = 'none'
if (firstListEl.className.includes('previousProduct')) {
setFirstChildren(previousEl);
} else if (secondListEl.className.includes('previousProduct')) {
setSecondChildren(previousEl);
} else if (thirdListEl.className.includes('previousProduct')) {
setThirdChildren(previousEl);
}
if (firstListEl.className.includes('currentProduct')) {
setFirstChildren(selectedEl);
} else if (secondListEl.className.includes('currentProduct')) {
setSecondChildren(selectedEl);
} else if (thirdListEl.className.includes('currentProduct')) {
setThirdChildren(selectedEl);
}
}
}, [currIdx])
return (
<div className="slide">
<div ref={firstRef} className="previousProduct">
{firstChildren}
</div>
<div ref={secondRef} className="currentProduct">
{secondChildren ? secondChildren : <img src={HTML} alt="img" /> }
</div>
<div ref={thirdRef} className="nextProduct">
{thirdChildren}
</div>
</div>
);
};
export default Slide;
왼쪽 가운데 오른쪽 슬라이드를 div
를 만들고 안에 들어갈 컨텐츠를 state 값이 들어가도록 했다.
현재 categoryIdx와 클릭한 categoryIdx를 비교하여 슬라이드의 방향을 적용해주기 위해
prevIdx 변수를 만들어주었는데 이 때 usePrevious 라는 커스텀 훅을 만들어 활용하였다.
custom hook
현재 값이 다음 값으로 바뀔 때 현재 값을 저장해 놓도록 만들었다.
import { useEffect, useRef } from "react";
export function usePrevious(value, defaultValue = undefined) {
const ref = useRef(defaultValue);
useEffect(() => {
ref.current = value;
})
return ref.current
}
슬라이드 모션을 구현하는 부분은 각 className을 가지고 잇는 tag를 찾아 상황에 맞게 className을 변경하고 transition 효과를 주었다.
const center = document.getElementsByClassName('currentProduct')[0];
const next = document.getElementsByClassName('nextProduct')[0];
const previous = document.getElementsByClassName('previousProduct')[0];
center.classList.remove('currentProduct');
next.classList.remove('nextProduct');
previous.classList.remove('previousProduct');
center.classList.add('previousProduct');
next.classList.add('currentProduct');
previous.classList.add('nextProduct');
center.style.transition = "transform 1s";
next.style.transition = "transform 1s";
previous.style.transition = "transform 1s";
previous.style.transition = 'none';
그리고 가장 고민했던 부분이 슬라이드가 넘어갈 때 현재 슬라이드를 유지하면서 다음 컨텐츠를 보여주는 부분이었다.
const previousEl = (<img src={prevImg} alt="img"/>)
const selectedEl = (<img src={selectedImg} alt="img"/>)
슬라이드가 움직이면서 넘어가는 화면을 previousEl
슬라이드가 움직이면서 넘어오는 화면을 selectedEl 을 지정해줘서
해당 클래스명을 가진 tag에 setState를 해주어 화면을 적용해준다.