์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
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 |
- ํ์ ์คํฌ๋ฆฝํธ
- ๋ผ์ดํธ๋ชจ๋
- styled-components
- ํด๋ก ์ฝ๋ฉ
- ๋ฐ์ํ์คํจ2
- ๋ ธ๋ง๋์ฝ๋
- reduce
- firebase
- ๊ฐ๋ฐ์๋ถํด๋ฝ
- sort
- ๋คํฌ๋ชจ๋
- onsnapshot
- ํฌํธํด๋ฆฌ์ค
- Today
- Total
rigood
[React] ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋๋ ๋ฐฉ๋ฒ 1 ๋ณธ๋ฌธ
[React] ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋๋ ๋ฐฉ๋ฒ 1
rigood 2022. 12. 19. 21:25๋ฆฌ์กํธ๋ก ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ฅผ ๋ง๋ค์ด ๋ด ์๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
๋ง๋๋ ๋ฐฉ๋ฒ์ 2๊ฐ์ง๊ฐ ์์ต๋๋ค.
1. position: absolute์ opacity๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ
2. tranform: translateX๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ
1๋ฒ ๋ฐฉ๋ฒ์ position: absolute๋ก ์ด๋ฏธ์ง๋ฅผ ๊ฒน์ณ ๋์ ์ํ์์
opacity๋ฅผ ์ด์ฉํ์ฌ ๋งจ ์์ ๋ณด์ฌ์ง๋ ์ด๋ฏธ์ง๋ฅผ ๋ฐ๊พธ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๊ฐํธํ ๋ฐฉ๋ฒ์ด์ง๋ง ์ด๋ฏธ์ง๊ฐ ์์ผ๋ก ์ค์ฝ- ์ด๋ํ๋ ํจ๊ณผ๋ ๋ผ ์ ์์ต๋๋ค.
2๋ฒ ๋ฐฉ๋ฒ์ ์ด๋ฏธ์ง๊ฐ ์์ผ๋ก ์ด๋ํ๋ ํจ๊ณผ๋ ๋ผ ์ ์์ง๋ง
๋ง์ง๋ง ์ฌ๋ผ์ด๋์์ ๋ค์ ์ฌ๋ผ์ด๋(๋งจ ์ฒ์ ์ฌ๋ผ์ด๋)๋ก ์ด๋ํ ๋
์๋ ๊ธธ์ ๋ค์ ๋๋์ ๊ฐ์ผ ํฉ๋๋ค.
์ด๋ฒ ๊ธ์์๋ 1๋ฒ ๋ฐฉ๋ฒ์ผ๋ก ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ฅผ ๋ง๋ค์ด๋ณด๊ณ ,
2๋ฒ ๋ฐฉ๋ฒ์ ๋ค์ ๊ธ์์ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
absolute์ z-index๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋ค๊ธฐ
โป CSS๋ styled-components ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
1. useState๋ก ๋งจ ์์ ๋ณด์ฌ์ง ์ฌ๋ผ์ด๋ ๋ฒํธ ์ง์
function Slider() {
const [slideIndex, setSlideIndex] = useState(1);
๋จผ์ useState๋ฅผ ์ด์ฉํ์ฌ slideIndex๋ผ๋ state๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
๋งจ ์์ ๋ณด์ฌ์ง ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋์ ๋ฒํธ๋ฅผ ๋ํ๋ด๋ state์ ๋๋ค.
dummy ๋ฐ์ดํฐ์ id๋ฅผ 1๋ถํฐ ์ง์ ํ๊ธฐ ๋๋ฌธ์ ์ด๊ธฐ๊ฐ์ 1๋ก ํด์ฃผ์์ต๋๋ค.
2. ์ฌ๋ผ์ด๋ ๋์ด, ์ด๋ ๋ฒํผ ์์ฑ
return (
<Container>
<Arrow direction="prev">์ด์ </Arrow>
{data.map((character) => (
<Slide
key={character.id}
className={character.id === slideIndex ? "active" : null}
>
<Photo
src={process.env.PUBLIC_URL + `/img/slider/${character.img}`}
/>
<Name>{character.name}</Name>
<Nickname>{character.nickname}</Nickname>
</Slide>
))}
<Arrow direction="next">๋ค์</Arrow>
</Container>
);
}
์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ฅผ ๋ด์ Container๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ ,
๊ทธ ์์ map ๋ฉ์๋๋ฅผ ํตํด Slide๋ฅผ ๋์ดํด์ค๋๋ค.
์ฌ๋ผ์ด๋์ id์ slideIndex๊ฐ ๊ฐ์ ๊ฒฝ์ฐ์๋ (๋งจ ์์ ๋ณด์ฌ์ง ์ฌ๋ผ์ด๋)
Slide ์ปดํฌ๋ํธ์ active ๋ผ๋ ํด๋์ค๋ฅผ ๋ถ์ฌํด์ค๋๋ค.
์ฌ๋ผ์ด๋ ์ด๋ ๋ฒํผ๋ ๋ง๋ค์ด ์ค๋๋ค.
Container ์์ Arrow ๋ผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด Slide ์ ์๋ ๋ฐฐ์นํ์์ต๋๋ค.
๋ฒํผ ๋ฐฉํฅ์ ๋ฐ๋ผ css ์์ฑ์ ๋ฌ๋ฆฌ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ props๋ก direction์ ์ ๋ฌํด์ฃผ์์ต๋๋ค.
3. CSS ์์ฑ ์ง์
const Container = styled.div`
// Container ํฌ๊ธฐ ์ง์
width: 400px;
height: 400px;
// ์ ์ฌ๋ฐฑ ์ง์ , Container ์ปดํฌ๋ํธ ๊ฐ์ด๋ฐ ์ ๋ ฌ
margin: 200px auto;
// Container ๋ด๋ถ ๊ฐ์ด๋ฐ ์ ๋ ฌ
display: flex;
justify-content: center;
align-items: center;
// position: absolute์ธ Arrow, Slide ์ปดํฌ๋ํธ๋ฅผ ๋ฐฐ์นํ๊ธฐ ์ํด
position: relative;
`;
const Arrow = styled.button`
// Container ์ปดํฌ๋ํธ ๊ธฐ์ค์ผ๋ก ๋ฐฐ์น
position: absolute;
// ์์ง ๊ฐ์ด๋ฐ ์ ๋ ฌ
top: 0;
bottom: 0;
margin: auto 0;
// Prev ๋ฒํผ์ธ ๊ฒฝ์ฐ left: 0, Next ๋ฒํผ์ธ ๊ฒฝ์ฐ right: 0
left: ${({ direction }) => direction === "prev" && "0px"};
right: ${({ direction }) => direction === "next" && "0px"};
// ํฌ๊ธฐ, ์คํ์ผ ์ง์
width: 50px;
height: 50px;
border-radius: 50%;
background-color: pink;
// Prev ๋ฒํผ์ด Slide ์ปดํฌ๋ํธ์ ๊ฐ๋ ค์ง์ง ์๊ธฐ ์ํด z-index ์ง์
z-index: 1;
`;
const Slide = styled.div`
// Container ์ปดํฌ๋ํธ ๊ธฐ์ค์ผ๋ก ๋ฐฐ์น
position: absolute;
// Slide ๋ด๋ถ ๊ฐ์ด๋ฐ ์ ๋ ฌ, ๊ฐญ ์ง์
display: flex;
flex-direction: column;
align-items: center;
row-gap: 20px;
// โ
โ
๊ธฐ๋ณธ์ ์ผ๋ก opacity๋ฅผ 0์ผ๋ก ํ์ฌ ์๋ณด์ด๊ฒ ํ๊ณ ,
// active ํด๋์ค๋ฅผ ํตํด opacity๋ฅผ 1๋ก ๋ฐ๊ฟ ๋ณด์ด๊ฒ ํจ
opacity: 0;
&.active {
opacity: 1;
}
// opacity์ ํธ๋์ง์
์ ์ฉ
transition: opacity 0.3s ease-in-out;
`;
const Photo = styled.img``;
const Name = styled.div``;
const Nickname = styled.div``;
Slide ์ปดํฌ๋ํธ๋ฅผ absolute๋ก ํฌ์ง์ ๋ ํ์ฌ
์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๊ฐ ๊ฒน์ณ ์์ด๋๋ก ๋ง๋ค์ด์ค๋๋ค.
โผ absolute ํฌ์ง์ ๋ ์ , ์ผ๋ ฌ๋ก ๋์ด๋์๋ ์ด๋ฏธ์ง๋ค์ด...
โผ absolute ํฌ์ง์ ๋ ํ ๊ฒน์ณ์ง ๋ชจ์ต
opacity๋ฅผ 0์ผ๋ก ์ง์ ํ์ฌ ๋ชจ๋ ์๋ณด์ด๊ฒ ๋ง๋ค์ด ์ค๋๋ค.
์ฌ๋ผ์ด๋ id์ slideIndex๊ฐ ์ผ์นํ๋ ๊ฒฝ์ฐ์๋
active ํด๋์ค๋ฅผ ํตํด opacity๊ฐ 1์ด ์ ์ฉ๋๋ฏ๋ก ํ๋ฉด ์์ ๋ํ๋๊ฒ ๋ฉ๋๋ค.
useState์ ์ด๊ธฐ๊ฐ์ด 1์ด๋ฏ๋ก ์ฒซ๋ฒ์งธ ์ด๋ฏธ์ง๊ฐ ํ๋ฉด์ ๋ํ๋ฉ๋๋ค.
โผ opacity ์กฐ์ ํ ๋ชจ์ต
4. ์ด์ , ๋ค์ ๋ฒํผ ํด๋ฆญ ํจ์ ๋ง๋ค๊ธฐ
์ด์ , ๋ค์ ๋ฒํผ ํด๋ฆญ ์ ์คํ๋ ํจ์๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ
onClick ์ด๋ฒคํธ์ ํด๋น ํจ์๋ฅผ ๋ฌ์์ฃผ์์ต๋๋ค.
const moveToPrevSlide = () => {
if (slideIndex !== 1) {
setSlideIndex((prev) => prev - 1);
} else {
setSlideIndex(data.length);
}
};
const moveToNextSlide = () => {
if (slideIndex !== data.length) {
setSlideIndex((prev) => prev + 1);
} else {
setSlideIndex(1);
}
};
<Arrow direction="prev" onClick={moveToPrevSlide}>
<Arrow direction="next" onClick={moveToNextSlide}>
๋จผ์ ์ด์ ๋ฒํผ ํด๋ฆญ ํจ์์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก, ์ด์ ๋ฒํผ ํด๋ฆญ์ slideIndex๋ฅผ 1์ฉ ์ค์ด๋,
์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋์ธ ๊ฒฝ์ฐ, ์ด์ ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฏ๋ก ๋งจ ๋ง์ง๋ง ์ฌ๋ผ์ด๋๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค.
if/else๋ฌธ๊ณผ setSlideIndex๋ฅผ ์ด์ฉํ์ฌ
slideIndex๊ฐ 1์ด ์๋ ๊ฒฝ์ฐ(์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋๊ฐ ์๋ ๊ฒฝ์ฐ) slideIndex๋ฅผ 1์ฉ ์ค์ด๊ณ ,
slideIndex๊ฐ 1์ธ ๊ฒฝ์ฐ(์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋์ธ ๊ฒฝ์ฐ) ๋งจ ๋ง์ง๋ง ์ฌ๋ผ์ด๋ ๋ฒํธ(=๋ฐฐ์ด ๊ธธ์ด)๋ก ๋ฐ๊ฟ์ค๋๋ค.
๋ค์ ๋ฒํผ ํด๋ฆญ ํจ์๋ ๋ก์ง์ ๋์ผํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก slideIndex๋ฅผ 1์ฉ ์ฆ๊ฐ์ํค๋,
๋งจ ๋ง์ง๋ง ์ฌ๋ผ์ด๋๋ง ๋ค์ ๋ฒํผ ํด๋ฆญ์ slideIndex๋ฅผ 1๋ก ๋ฐ๊ฟ์ค๋๋ค.
โผ ๋ฒํผ ํด๋ฆญ ์ ์ด๋ฏธ์ง๊ฐ ๋ฐ๋๋ ๋ชจ์ต
5. ํ์ด์ง๋ค์ด์ ๊ตฌํํ๊ธฐ
const moveDot = (index) => {
setSlideIndex(index);
};
<DotContainer>
{data.map((character) => (
<Dot
key={character.id}
className={character.id === slideIndex ? "active" : null}
onClick={() => moveDot(character.id)}
/>
))}
</DotContainer>
ํ์ด์ง๋ค์ด์ ๋ฒํผ์ ๋ด์ DotContainer๋ฅผ ๋ง๋ค์ด ์ฃผ๊ณ ,
์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ฅผ ๋์ดํ๋ ๊ฒ์ฒ๋ผ Dot๋ ๋์ดํด์ค๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, id์ ํ์ฌ slideIndex๊ฐ ๊ฐ์ ๊ฒฝ์ฐ active๋ผ๋ ํด๋์ค๋ฅผ ๋ฌ์์ค๋๋ค.
Dot์ ํด๋ฆญํ๋ฉด ํ์ฌ id๋ฅผ ์ธ์๋ก ๋ด์ moveDot ํจ์๋ก ์ ๋ฌํ์ฌ
slideIndex๋ฅผ ๋ฐ๊ฟ์ค๋๋ค. ๊ทธ๋ผ ํด๋น ๋ฒํธ์ ๋ง๋ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ก ๋ฐ๋๊ฒ ๋ฉ๋๋ค.
const DotContainer = styled.div`
// Container ์ปดํฌ๋ํธ ๊ธฐ์ค์ผ๋ก ๋งจ ์๋ ๋ฐฐ์น
position: absolute;
bottom: 0;
// ๋๋น ์ง์
width: 150px;
// Dot ๋ฐฐ์น
display: flex;
justify-content: space-between;
`;
const Dot = styled.div`
width: 12px;
height: 12px;
border-radius: 50%;
background-color: pink;
cursor: pointer;
// ํ์ฌ ์ฌ๋ผ์ด๋๋ฅผ ๋ํ๋ด๋ Dot์ ๋ฐฐ๊ฒฝ์์ ๋ค๋ฅด๊ฒ ์ค์
&.active {
background-color: skyblue;
}
`;
DotContainer๋ position: absolute ์์ฑ์ ํตํด Container ๋งจ ์๋ ์ชฝ์ ๋ฐฐ์นํด์ค๋๋ค.
Dot ์ปดํฌ๋ํธ์์๋ active ํด๋์ค๋ฅผ ํตํด ํ์ฌ ํ์ฑํ๋ Dot๋ง ๋ฐฐ๊ฒฝ์์ ๋ค๋ฅด๊ฒ ์ค์ ํด์ค๋๋ค.
์ฌ๊ธฐ๊น์ง ํ๋ฉด ์์ฑ์ ๋๋ค^^
์ ์ฒด ์ฝ๋
import { useState } from "react";
import styled from "styled-components";
import data from "./data";
function Slider() {
const [slideIndex, setSlideIndex] = useState(1);
const moveToPrevSlide = () => {
if (slideIndex !== 1) {
setSlideIndex((prev) => prev - 1);
} else {
setSlideIndex(data.length);
}
};
const moveToNextSlide = () => {
if (slideIndex !== data.length) {
setSlideIndex((prev) => prev + 1);
} else {
setSlideIndex(1);
}
};
const moveDot = (index) => {
setSlideIndex(index);
};
return (
<Container>
<Arrow direction="prev" onClick={moveToPrevSlide}>
์ด์
</Arrow>
{data.map((character) => (
<Slide
key={character.id}
className={character.id === slideIndex ? "active" : null}
>
<Photo
src={process.env.PUBLIC_URL + `/img/slider/${character.img}`}
/>
<Name>{character.name}</Name>
<Nickname>{character.nickname}</Nickname>
</Slide>
))}
<Arrow direction="next" onClick={moveToNextSlide}>
๋ค์
</Arrow>
<DotContainer>
{data.map((character) => (
<Dot
key={character.id}
className={character.id === slideIndex ? "active" : null}
onClick={() => moveDot(character.id)}
/>
))}
</DotContainer>
</Container>
);
}
export default Slider;
const Container = styled.div`
width: 400px;
height: 400px;
margin: 200px auto;
display: flex;
justify-content: center;
align-items: center;
position: relative;
`;
const Arrow = styled.button`
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
left: ${({ direction }) => direction === "prev" && "0px"};
right: ${({ direction }) => direction === "next" && "0px"};
width: 50px;
height: 50px;
border-radius: 50%;
background-color: pink;
z-index: 1;
`;
const Slide = styled.div`
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 20px;
opacity: 0;
transition: opacity 0.3s ease-in-out;
&.active {
opacity: 1;
}
`;
const Photo = styled.img``;
const Name = styled.div``;
const Nickname = styled.div``;
const DotContainer = styled.div`
position: absolute;
bottom: 0;
width: 150px;
display: flex;
justify-content: space-between;
`;
const Dot = styled.div`
width: 12px;
height: 12px;
border-radius: 50%;
background-color: pink;
cursor: pointer;
&.active {
background-color: skyblue;
}
`;
// data.js
const data = [
{
id: 1,
name: "์ฐ์์ฐ",
nickname: "์ฒ์ฌ ๋ณํธ์ฌ",
img: "youngwoo.jpg",
},
{
id: 2,
name: "์ด์คํธ",
nickname: "์ญ์ญํ๋ฐ์",
img: "junho.jpg",
},
{
id: 3,
name: "์ ๋ช
์",
nickname: "์ ๋์ฝ ์์ฌ",
img: "myeongseok.jpg",
},
{
id: 4,
name: "์ต์์ฐ",
nickname: "๋ด๋ ์ ํ์ด",
img: "suyeon.jpg",
},
{
id: 5,
name: "๊ถ๋ฏผ์ฐ",
nickname: "๊ถ๋ชจ์ ์",
img: "minwoo.jpg",
},
];
export default data;