Skip to content

Commit

Permalink
Merge pull request #85 from codestates-seb/feat_carousel
Browse files Browse the repository at this point in the history
feat: carousel 컴포넌트 구현 #56 - useusername1
  • Loading branch information
useusername1 authored Jan 25, 2023
2 parents 8eddf00 + c5b67bd commit f142751
Show file tree
Hide file tree
Showing 9 changed files with 659 additions and 45 deletions.
16 changes: 16 additions & 0 deletions project/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
"@types/styled-react-modal": "^1.2.2",
"aws-sdk": "^2.1296.0",
"axios": "^1.2.2",
"blurhash": "^2.0.4",
"hangul-js": "^0.2.6",
"http-proxy-middleware": "^2.0.6",
"react": "^18.2.0",
"react-blurhash": "^0.3.0",
"react-daum-postcode": "^3.1.1",
"react-dom": "^18.2.0",
"react-geocode": "^0.2.3",
Expand Down
22 changes: 7 additions & 15 deletions project/src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Place from "./pages/Place";
import Post from "./pages/Post";
import { Header } from "./components/Header/index";
import HiddenHeader from "./components/Header/HiddenHeader";

import Carousel from "./components/Carousel";

const Body = styled.div`
width: 83.5%;
Expand All @@ -20,10 +20,7 @@ const Footer = styled.div`
height: 157px;
`;



function Main() {

return (
<>
<Header>
Expand All @@ -32,19 +29,15 @@ function Main() {
</Header>
{/* <HiddenHeader /> */}
{/* <FixedOnScrollUpHeader /> */}
<Body>

</Body>
<Carousel />
<Body></Body>
<Footer>footer</Footer>
</>
);
}

export default Main;




// import axios from "axios";
// import ButtonForm from "./components/Button"

Expand All @@ -56,8 +49,6 @@ export default Main;
// LoggedUser,
// } from "./recoil/state";



// const [isLogin, setIslogin] = useRecoilState(LoginState);
// const [auth, setAuth] = useRecoilState(AuthToken);
// const [rafresh, setRefresh] = useRecoilState(RefreshToken);
Expand All @@ -79,7 +70,7 @@ export default Main;
// }
// })
// .then((res) => {
// console.log(res)
// console.log(res)
// console.log("로긴성공")

// // navigate("/");
Expand All @@ -88,5 +79,6 @@ export default Main;

// }


{/* <button onClick={onClickBtn}>버튼버튼</button> */}
{
/* <button onClick={onClickBtn}>버튼버튼</button> */
}
84 changes: 84 additions & 0 deletions project/src/components/Carousel/CarouselContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { memo, useState } from "react";
import {
CarouselItemContainer,
CarouselTextWrapper,
LeftCarouselTextWrapper,
RightCarouselTextWrapper,
ImageWrapper,
} from "./style";
import { IoIosArrowForward as RightArrowIcon } from "react-icons/io";
import { MdLocationOn as PinIcon } from "react-icons/md";
import { Blurhash } from "react-blurhash";
interface CarouselContentProps {
data: {
img: string;
id: number;
title: string;
subtitle: string;
link: string;
color: string;
blur_hash: string;
location: string;
};
isTransitionEnd: boolean;
}
const CarouselContent = memo(
({ data, isTransitionEnd }: CarouselContentProps) => {
const [imgLoaded, setImgLoaded] = useState(false);
const {
img: imgUrl,
id,
title,
subtitle,
link,
color,
blur_hash,
location,
} = data;

const handleImgLoad = () => {
setImgLoaded(true);
};

return (
<CarouselItemContainer>
<ImageWrapper isLoaded={imgLoaded}>
<Blurhash
hash={blur_hash}
style={{ width: "100%", height: "100%" }}
/>
<img
src={imgUrl}
alt={`carousel${id}`}
loading={id === 3 ? "eager" : "lazy"}
onLoad={handleImgLoad}
/>
</ImageWrapper>
<CarouselTextWrapper>
<LeftCarouselTextWrapper
isTransitionEnd={isTransitionEnd}
textColor={color}
>
<h2>{title}</h2>
<h3>{subtitle}</h3>
<a href={link}>
더 보러가기
<RightArrowIcon className="right-arrow" />
</a>
</LeftCarouselTextWrapper>
<RightCarouselTextWrapper
isTransitionEnd={isTransitionEnd}
textColor={color}
>
<span>
<PinIcon className="location-pin" />
{location}
</span>
</RightCarouselTextWrapper>
</CarouselTextWrapper>
</CarouselItemContainer>
);
}
);

export default CarouselContent;
172 changes: 172 additions & 0 deletions project/src/components/Carousel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useState, useEffect, useRef, useLayoutEffect, Fragment } from "react";
import CarouselContent from "./CarouselContent";
import carouselData from "../../data/CarouselData";
import {
CarouselWrapper,
CarouselContentListContainer,
CarouselControlContainer,
RangeSliderContainer,
SliderDot,
} from "./style";
import { IoIosArrowForward as NextIcon } from "react-icons/io";
import { IoIosArrowBack as PrevIcon } from "react-icons/io";
import { IoPause as PauseIcon } from "react-icons/io5";
import { IoPlay as PlayIcon } from "react-icons/io5";

const newCarouselData = [
{ ...carouselData[carouselData.length - 1], id: 20 },
...carouselData,
{ ...carouselData[0], id: 21 },
];

const INTERVAL = 6000;
const Carousel = () => {
const [currentPhoto, setCurrentPhoto] = useState<number>(1); //현재 캐러셀 위치
const [textTransition, setTextTransition] = useState<number>(-1); //textTransition이 일어나야할 번호
const [isPlaying, setIsPlaying] = useState(true); //재생버튼
const transitionOnRef = useRef<boolean>(false); //전체 캐러셀 애니메이션 관리
const autoCarouselTimerIdRef = useRef<NodeJS.Timeout | null>(null); //setInterval id
const preUpdatedCurrentPhotoRef = useRef<number>(1);

useLayoutEffect(() => {
setTimeout(() => {
switch (currentPhoto) {
case 0:
setCurrentPhoto(newCarouselData.length - 2);
setTextTransition(newCarouselData.length - 2);
transitionOnRef.current = false;
break;
case newCarouselData.length - 1:
setCurrentPhoto(1);
setTextTransition(1);
transitionOnRef.current = false;
break;
default:
transitionOnRef.current = false;
setTextTransition(currentPhoto);
}
}, 500);
}, [currentPhoto]);

useEffect(() => {
setTextTransition(currentPhoto);

autoCarouselTimerIdRef.current = setInterval(() => {
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
}, INTERVAL);
return () =>
clearInterval(autoCarouselTimerIdRef.current as NodeJS.Timeout);
}, []);

if (currentPhoto === 0) {
preUpdatedCurrentPhotoRef.current = newCarouselData.length - 2;
} else if (currentPhoto === newCarouselData.length - 1) {
preUpdatedCurrentPhotoRef.current = 1;
} else {
preUpdatedCurrentPhotoRef.current = currentPhoto;
}

//transitionOnRef.current가 false가 되는 경우: 맨 마지막 이미지 또는 맨 처음 이미지에 도달하고 transition이 끝났을 때
//true가 되는 경우: 다음 이미지 번호로 상태를 업데이트 할 때
const handlePrevClick = () => {
if (!transitionOnRef.current) {
clearInterval(autoCarouselTimerIdRef.current as NodeJS.Timeout);
transitionOnRef.current = true;
setCurrentPhoto((p) => p - 1);
if (isPlaying) {
autoCarouselTimerIdRef.current = setInterval(() => {
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
}, INTERVAL);
}
}
};
const handleNextClick = () => {
if (!transitionOnRef.current) {
clearInterval(autoCarouselTimerIdRef.current as NodeJS.Timeout);
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
if (isPlaying) {
autoCarouselTimerIdRef.current = setInterval(() => {
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
}, INTERVAL);
}
}
};
const handlePauseClick = () => {
if (autoCarouselTimerIdRef.current) {
clearInterval(autoCarouselTimerIdRef.current as NodeJS.Timeout);
setIsPlaying(false);
autoCarouselTimerIdRef.current = null;
}
};
const handlePlayClick = () => {
if (!autoCarouselTimerIdRef.current) {
autoCarouselTimerIdRef.current = setInterval(() => {
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
}, INTERVAL);
setIsPlaying(true);
}
};

const handleDotClick = (i: number) => {
if (!transitionOnRef.current) {
clearInterval(autoCarouselTimerIdRef.current as NodeJS.Timeout);
transitionOnRef.current = true;

setCurrentPhoto(i);
if (isPlaying) {
autoCarouselTimerIdRef.current = setInterval(() => {
transitionOnRef.current = true;
setCurrentPhoto((p) => p + 1);
}, INTERVAL);
}
}
};

return (
<>
<CarouselWrapper>
<CarouselContentListContainer
currentPhotoNum={currentPhoto}
transitionOn={transitionOnRef.current}
>
{newCarouselData.map((el, i) => (
<CarouselContent
key={el.id}
data={el}
isTransitionEnd={i === textTransition}
/>
))}
</CarouselContentListContainer>
<CarouselControlContainer>
<PrevIcon className="prev-icon" onClick={handlePrevClick} />
{isPlaying ? (
<PauseIcon className="pause-icon" onClick={handlePauseClick} />
) : (
<PlayIcon className="play-icon" onClick={handlePlayClick} />
)}

<NextIcon className="next-icon" onClick={handleNextClick} />
</CarouselControlContainer>
<RangeSliderContainer currentPhoto={currentPhoto}>
{newCarouselData.map((el, i) =>
i === 0 || i === newCarouselData.length - 1 ? (
<Fragment key={el.id}></Fragment>
) : (
<SliderDot
key={el.id}
currentDot={i === preUpdatedCurrentPhotoRef.current}
onClick={() => handleDotClick(i)}
/>
)
)}
</RangeSliderContainer>
</CarouselWrapper>
</>
);
};
export default Carousel;
Loading

0 comments on commit f142751

Please sign in to comment.