티스토리 뷰

react

리액트로 라이브러리 없이 캐러셀 슬라이드 만들기

YG - 96년생 , 강아지 있음, 개발자 희망 2022. 1. 25. 09:52

예제

헤더와 슬라이드를 구현 할 것

배포 링크

https://fervent-hugle-5ea8ae.netlify.app/

 

커리어 여정을 행복하게, 원티드

 

fervent-hugle-5ea8ae.netlify.app

 

필요한 것

useState, useRef, useEffect

import { useState, useRef, useEffect } from "react";

 

 

사용할 오브젝트

const WantedImg = [
  "https://static.wanted.co.kr/images/banners/1489/312a0c29.jpg",
  "https://static.wanted.co.kr/images/banners/1486/fba2df30.jpg",
  "https://static.wanted.co.kr/images/banners/1468/3df61cbc.jpg",
  "https://static.wanted.co.kr/images/banners/1490/0b775035.jpg",
  "https://static.wanted.co.kr/images/banners/1484/b2853456.jpg",
  "https://static.wanted.co.kr/images/banners/1460/619f3af7.jpg",
  "https://static.wanted.co.kr/images/banners/1473/41f7b36e.jpg",
  "https://static.wanted.co.kr/images/banners/1487/0d36f0b5.jpg",
  "https://static.wanted.co.kr/images/banners/1488/baa54448.jpg",
];

const wantedTitle = [
  "22년 달라지는 노동법령",
  "성과를 내는 마케팅",
  "해, 커리어 EP 02 공개",
  "마케팅 주니어를 찾습니다",
  "성장하는 개발자가 되려면?",
  "개발자 성장 비결 공개!",
  "개발자 되고 싶은 분들!?",
  "포트폴리오를 부탁해!",
  "UX 디자이너의 커리어 설계",
];

const wantedDes = [
  "노무관리 쟁점 한 눈에 파악하기",
  "실제 사례를 공개합니다!",
  "마지막 관문 2라운드의 승자는?",
  "기업 과제 풀고 취업까지 한번에!",
  "OOO 검색하지 말 것!",
  "Velog, 글 쓰는 개발자들의 이야기",
  "프론트엔드 무료 교육과정 참여하기",
  "디자이너의 포폴 살펴보기",
  "브랜드 가치를 더하는 디자인",
];

 

1. 슬라이드 만들기

 

  //슬라이드
  const slideRef = useRef(null);
  const [index, setIndex] = useState(0); // 인덱스를 만들어줍니다.
  const [isSlide, setIsSlide] = useState(false); // 슬라이드 중인지 체크해줍니다. 슬라이드 중에 여러번 빠르게 클릭 못하게 하는 역할
  const [x, setX] = useState(0); // css에서 슬라이드 애니메이션 효과를 주기위해 x만큼 이동시키는 역할입니다.


      <Row
        key={index}
        onMouseDown={onMouseDown}
        onMouseUp={onMouseUp}
        onMouseLeave={onMouseLeave}
        onMouseMove={onMouseMove}
        ref={slideRef}
        style={{
          transform: `translateX(${x}vw)`,
        }}
      >
        <Container>
          <PrivewImg
            style={{
              opacity: 0.5,
              width: windowWidth > 1200 ? null : `80vw`,
              height:
                windowWidth > 1200
                  ? null
                  : windowWidth < 770
                  ? "185px"
                  : "250px",
            }}
            src={WantedImg[morePrevImg]}
          ></PrivewImg>
        </Container>
        <Container>
          <PrivewImg
            style={{
              opacity: 0.5,
              width: windowWidth > 1200 ? null : `80vw`,
              height:
                windowWidth > 1200
                  ? null
                  : windowWidth < 770
                  ? "185px"
                  : "250px",
            }}
            src={WantedImg[PrevImg]}
          ></PrivewImg>
        </Container>
        <ImgWrapper>
          <Img
            style={{
              opacity: 1,
              width: windowWidth > 1200 ? null : `80vw`,
              height:
                windowWidth > 1200
                  ? null
                  : windowWidth < 770
                  ? "185px"
                  : "250px",
            }}
            src={WantedImg[index]}
          />
          {!isSlide && windowWidth > 1200 ? (
            <ImgDes>
              <Title>{wantedTitle[index]}</Title>
              <Des>{wantedDes[index]}</Des>
              <LinkSpan>
                바로가기<i class="fas fa-chevron-right"></i>
              </LinkSpan>
            </ImgDes>
          ) : null}
          {!isSlide && windowWidth <= 1200 ? (
            <MiniWrapper>
              <MiniTitle>{wantedTitle[index]}</MiniTitle>
              <MiniDes>{wantedDes[index]}</MiniDes>
              <LinkSpan>
                바로가기<i class="fas fa-chevron-right"></i>
              </LinkSpan>
            </MiniWrapper>
          ) : null}
        </ImgWrapper>
        <Container>
          <PrivewImg
            style={{
              opacity: 0.5,
              width: windowWidth > 1200 ? null : `80vw`,
              height:
                windowWidth > 1200
                  ? null
                  : windowWidth < 770
                  ? "185px"
                  : "250px",
            }}
            src={WantedImg[NextImg]}
          ></PrivewImg>
        </Container>
        <Container>
          <PrivewImg
            style={{
              opacity: 0.5,
              width: windowWidth > 1200 ? null : `80vw`,
              height:
                windowWidth > 1200
                  ? null
                  : windowWidth < 770
                  ? "185px"
                  : "250px",
            }}
            src={WantedImg[moreNextImg]}
          ></PrivewImg>
        </Container>
      </Row>

총 5개의 화면을 구성하고 클릭을 할 때마다 인덱스가 줄거나 커짐에 따라 화면을 구성하는 텍스트와 이미지를 배열순서를 이용하여 슬라이드처럼 이미지가 변하도록 하였습니다.

 

  const increaseClick = async () => {
    if (isSlide) {
      return;
    }
    setX(-56);
    setIsSlide(true);
    await setTimeout(() => {
      setIndex((prev) => (prev === 8 ? 0 : prev + 1));
      setX(0);
      setIsSlide(false);
    }, 500);
    //setIndex((prev) => (prev === 7 ? 0 : prev + 1));
  };
  const decreaseClick = async () => {
    if (isSlide) {
      return;
    }
    setX(+56);
    setIsSlide(true);
    await setTimeout(() => {
      setIndex((prev) => (prev === 0 ? 8 : prev - 1));
      setX(0);
      setIsSlide(false);
    }, 500);
  };
  const morePrevImg = index === 1 ? 8 : index === 0 ? 7 : index - 2;
  const PrevImg = index === 0 ? 8 : index - 1;
  const NextImg = index === 8 ? 0 : index + 1;
  const moreNextImg = index === 8 ? 1 : index === 7 ? 0 : index + 2;
  //console.log(slideRef.current);
  //console.log(index);

 

const Wrapper = styled.div`
  margin: 22px 0px;
  display: flex;
  overflow-x: hidden;

  align-items: center;
`;


const Row = styled.div`
  width: 100vw;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  transition: all 0.5s ease-in-out;
`;

<Wraapper>
<Row
        key={index}
        onMouseDown={onMouseDown}
        onMouseUp={onMouseUp}
        onMouseLeave={onMouseLeave}
        onMouseMove={onMouseMove}
        ref={slideRef}
        style={{
          transform: `translateX(${x}vw)`,
        }}
      >

 

클릭하였을 때 인덱스를 증가하거나 줄이는 함수입니다.

여기서 setX를 +-56을 하였는데 x의 값을 변하게 하여 Row 에 있는 style을 통해 이미지가 옆으로 넘어가는 느낌을 주려고 했기 때문입니다. 그리고 저는 justify-content를 center에 두고  Row를 감싸는 Wrapper에는 overflow hidden을 주어서 5개의 이미지중 가운데인 3번째 이미지를 보이도록 하였습니다.

 

그래서 setX를 +- 56을 해주어서 이동하는 애니메이션 효과를 준 뒤 setTimeout을 이용하여 500ms 뒤에  다시 x를 0으로 돌려서 화면 스크롤을 가운데로 오도록 하였습니다.

 

2.드래그로 슬라이드 이동시키기

 //드래그로 슬라이드 넘기기
  const [isClick, setIsClick] = useState(false); // 드래그를 시작하는지 체크해줍니다.
  const [mouseDownClientX, setMouseDownClientX] = useState(0); // 마우스를 클릭한 지점의 x 좌료를 저장합니다
  const [mouseUpClientX, setMouseUpClientX] = useState(0); // 마우스를 땐 지점의 x 좌표를 저장합니다.

const onMouseDown = (event) => {
    setIsClick(true);
    setMouseDownClientX(event.pageX);
    console.log(slideRef);
  };
  const onMouseLeave = (event) => {
    setIsClick(false);
  };
  const onMouseUp = (event) => {
    setIsClick(false);
    const imgX = mouseDownClientX - mouseUpClientX;
    // console.log(imgX);
    if (imgX < -100) {
      slideRef.current.style.transform = `translateX(${imgX}px)`;
      increaseClick();
    } else if (imgX > 100) {
      slideRef.current.style.transform = `translateX(${imgX}px)`;
      decreaseClick();
    }
  };
  const onMouseMove = (event) => {
    if (!isClick) return;
    event.preventDefault();
    setMouseUpClientX(event.pageX);
    const imgX = mouseDownClientX - mouseUpClientX;
    if (Math.abs(imgX) > 100) {
      // slideRef.current.style.transform = `translateX(${imgX}px)`;
    }
  };
  
  </LeftButton>
      <Row
        key={index}
        onMouseDown={onMouseDown}
        onMouseUp={onMouseUp}
        onMouseLeave={onMouseLeave}
        onMouseMove={onMouseMove}
        ref={slideRef}
        style={{
          transform: `translateX(${x}vw)`,
        }}
      >

제가 사용한 방법을 설명해드리자면 마우스를 클릭하였을 때 마우스가 클릭한 지점의 x 값을 저장하기 위해 mouse 이벤트 중 하나인 mouseDown을 이용하였고 마우스를 땐 지점의 x 값을 저장하기 위해 mouseLeave를 사용하였습니다.

 

그래서 두 값을 두 개의 변수에 따로 저장하여 두 값의 차이가 100 이상이라면 슬라이드를 넘기는 함수를 실행하도록 하였고 애니메이션 효과도 주어야 하기 때문에 useRef를 이용하여 Row에 ref={sliderRef} 를 해두어 Row의 style을 ref를 이용하여 효과를 주었습니다. 

 

3.시간이 지나면 자동으로 슬라이드 하기

 useEffect(() => {
    const autoPage = setTimeout(() => {
      setX(-56);
      setIsSlide(true);
      setTimeout(() => {
        setIndex((prev) => (prev === 8 ? 0 : prev + 1));
        setX(0);
        setIsSlide(false);
      }, 500);
    }, 5000);
    return () => {
      clearTimeout(autoPage);
    };
  }, [index, isClick]);

 

useEffect 와 setTimeout 을 이용해 index,isClick 값이 변할때 실행되도록 하였습니다.

 

4. 반응형 사이트

 const [windowWidth, setWindowWidth] = useState(window.innerWidth); // 사용자의 화면크기 정보를 받아 반응형 사이트에 사용합니다.
 
   const resizeWidth = () => {
    setWindowWidth(window.innerWidth);
  };

  useEffect(() => {
    window.addEventListener("resize", resizeWidth);
    return () => {
      window.removeEventListener("resize", resizeWidth);
    };
  }, []);

화면 크기가 변함에 따라 css나 본문의 내용을 변경할 때 사용하기위한 변수를 만든 뒤 window.innerWidth 를 이용해 유저의 화면크기를 변수에 저장하고 useEffect에 window.addEventLister 을 사용하여 resize 될 때 이벤트에 반응하도록 하여 변수값을 변경해주었습니다.

 

 

좀더 자세한 코드는 깃허브에 가시면 보실 수 있습니다.

 

https://github.com/Gilpop8663/wanted_pre_onboarding

 

GitHub - Gilpop8663/wanted_pre_onboarding: 원티드 프리온보딩 코스

원티드 프리온보딩 코스. Contribute to Gilpop8663/wanted_pre_onboarding development by creating an account on GitHub.

github.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함