개발/개발일지

무한 슬라이드 구현 (with React, TypeScript, styled-component)

pizzaYami 2023. 11. 17.

 

무한 슬라이드(?)를 구현해 보았다.

 

1. setTimeout을 통해서 이 데이터를 하나씩 추가되도록 만들어 데이터가 끊임없이 만들어지도록 만들고

2. transform: translate3 d(0px, 0px, 0px)};을 활용하여 x축에 해당되는 첫 번째 인자가 계속해서 증가하는 -값으로 만들고

3. 좌우 그러데이션으로 사라지게 하기

 

1. 데이터가 계속해서 생기게 만들기

 

무한 슬라이드가 되도록 만들려면 데이터가 끊임없이 생성되어야 한다.

slideNum가 2초마다 1씩 증가하도록 만들고 또한 WorkSlideData도 2초마다 순차적으로 하나씩 데이터가 들어가도록 만들었다.

첫 번째 데이터를 0으로 넣은 이유는 렌더링 되는 과정에서 첫 번째 데이터가 두 개가 생성되기 때문에 이를 숨기기 위해 빈 데이터를 넣었다.

const WorkSlideData = [
  {
    key: 0,
    img: '',
  },
  {
    key: 0,
    img: awwords,
  },
  {
    key: 1,
    img: byrdie,
  },
  {
    key: 2,
    img: cut,
  },
  {
    key: 3,
    img: digiday,
  },
  {
    key: 4,
    img: forbes,
  },
  {
    key: 5,
    img: mindsParkls,
  },
];

const WorkSlide = () => {
  const [slideNum, setSlideNum] = useState(0);
  
  setTimeout(() => {
    setSlideNum((prev) => {
      return prev + 1;
    });
    WorkSlideData.push(WorkSlideData[slideNum % 6]);
  }, 2000);

  return (
    <Container>
      <ItemContainer>
        <ItemS interval={interval}>
          {WorkSlideData.map((data, index) => (
            <img key={index} src={data.img} alt={String(data.img)} />
          ))}
        </ItemS>
      </ItemContainer>
    </Container>
  );
};

 

2. X축 변화되도록 만들기

슬라이드가 자동으로 움직이게 하기 위해서는 x축이 계속해서 변화될 필요가 있다.

이걸 setTimeout과 props로 해결을 하였다.

setTimeout으로 2초마다 interval의 데이터가 -80씩 감소하도록 만들었고 transition을 넣어서 움직이도록 만들었다.

const WorkSlide = () => {
  const [interval, setInterval] = useState(-200);
 
  setTimeout(() => {
    setInterval((prev) => {
      return prev - 80;
    });
  }, 2000);

  return (
    <Container>
      <ItemContainer>
        <ItemS interval={interval}>
          {WorkSlideData.map((data, index) => (
            <img key={index} src={data.img} alt={String(data.img)} />
          ))}
        </ItemS>
      </ItemContainer>
    </Container>
  );
};

const ItemS = styled.div<{ interval: number }>`
  transform: ${(props) => `translate3d(${props.interval}px, 0px, 0px)`};
  transition: transform 2000ms linear 0s;
  display: flex;
  img {
    float: left;
    width: 100%;
    height: 24px;
    align-items: center;
    justify-content: center;
    padding: 0 35px;
  }
`;

 

3. 좌우 그러데이션으로 사라지게 하기

박스를 하나 만들고 

background-image: linear-gradient(to left, #fff 0%, #fff 50%, rgba(255, 255, 255, 0) 100%); position: absolute;

linear-gradient를 사용하여 그러데이션을 주고 z-index를 넣어서 서서히 사라지고 서서히 생성되도록 만들었다.

const WorkSlide = () => {
  const [isLazy, setIsLazy] = useState(false);
  const [interval, setInterval] = useState(-200);
  const [slideNum, setSlideNum] = useState(0);

  useEffect(() => {

  return (
    <Container>
      <ItemContainer>
        <ItemS interval={interval}>
          {WorkSlideData.map((data, index) => (
            <img key={index} src={data.img} alt={String(data.img)} />
          ))}
        </ItemS>
      </ItemContainer>
    </Container>
  );
};


const ItemContainer = styled.div`
  margin: 23px -39px;
  position: relative;
  &::before {
    content: '';
    width: 50px;
    height: 50px;
    margin: 10px 25px 6px 0;
    background-image: linear-gradient(to right, #fff 0%, #fff 50%, rgba(255, 255, 255, 0) 100%);
    position: absolute;
    left: 0;
    top: -24px;
    z-index: 99;
  }
  &::after {
    content: '';
    width: 50px;
    height: 50px;
    margin: 10px 0 6px 25px;
    background-image: linear-gradient(to left, #fff 0%, #fff 50%, rgba(255, 255, 255, 0) 100%);
    position: absolute;
    right: 0;
    top: -24px;
    z-index: 99;
  }
`;

완성된 코드

import { useEffect, useState } from 'react';
import { styled } from 'styled-components';
import awwords from '../../img/awwords.svg';
import byrdie from '../../img/byrdie.svg';
import cut from '../../img/cut.svg';
import digiday from '../../img/digiday.svg';
import forbes from '../../img/forbes.svg';
import mindsParkls from '../../img/mindsParkls.svg';

const WorkSlideData = [
  {
    key: 0,
    img: '',
  },
  {
    key: 0,
    img: awwords,
  },
  {
    key: 1,
    img: byrdie,
  },
  {
    key: 2,
    img: cut,
  },
  {
    key: 3,
    img: digiday,
  },
  {
    key: 4,
    img: forbes,
  },
  {
    key: 5,
    img: mindsParkls,
  },
];

const WorkSlide = () => {
  const [isLazy, setIsLazy] = useState(false);
  const [interval, setInterval] = useState(-200);
  const [slideNum, setSlideNum] = useState(0);

  useEffect(() => {
    const lazyTimeout = setTimeout(() => setIsLazy(true), 500);
    return () => clearTimeout(lazyTimeout);
  }, []);

  setTimeout(() => {
    setInterval((prev) => {
      return prev - 80;
    });
    setSlideNum((prev) => {
      return prev + 1;
    });
    WorkSlideData.push(WorkSlideData[slideNum % 6]);
  }, 2000);

  return (
    <Container className={isLazy ? 'active' : ''}>
      <ItemContainer>
        <ItemS interval={interval}>
          {WorkSlideData.map((data, index) => (
            <img key={index} src={data.img} alt={String(data.img)} />
          ))}
        </ItemS>
      </ItemContainer>
    </Container>
  );
};

export default WorkSlide;

const Container = styled.div`
  margin: 50px 0 85px 0;
  width: 100%;
  padding: 0 10px;
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    background: currentColor;
    height: 1px;
    width: 0px;
    opacity: 0.5;
  }
  &::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    background: currentColor;
    height: 1px;
    width: 0px;
    opacity: 0.5;
  }
  &.active {
    transform: translate(0);
    opacity: 1;
    transition: all 0.9s ease-in;
    &::after {
      width: 100%;
      transition: width 0.9s ease-in;
    }
    &::before {
      width: 100%;
      transition: width 0.9s ease-in;
    }
  }
`;

const ItemContainer = styled.div`
  margin: 23px -39px;
  position: relative;
  &::before {
    content: '';
    width: 50px;
    height: 50px;
    margin: 10px 25px 6px 0;
    background-image: linear-gradient(to right, #fff 0%, #fff 50%, rgba(255, 255, 255, 0) 100%);
    position: absolute;
    left: 0;
    top: -24px;
    z-index: 99;
  }
  &::after {
    content: '';
    width: 50px;
    height: 50px;
    margin: 10px 0 6px 25px;
    background-image: linear-gradient(to left, #fff 0%, #fff 50%, rgba(255, 255, 255, 0) 100%);
    position: absolute;
    right: 0;
    top: -24px;
    z-index: 99;
  }
`;

const ItemS = styled.div<{ interval: number }>`
  transform: ${(props) => `translate3d(${props.interval}px, 0px, 0px)`};
  transition: transform 2000ms linear 0s;
  display: flex;
  img {
    float: left;
    width: 100%;
    height: 24px;
    align-items: center;
    justify-content: center;
    padding: 0 35px;
  }
`;

댓글