์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
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 |
- ํฌํธํด๋ฆฌ์ค
- ๋ ธ๋ง๋์ฝ๋
- ๋ผ์ดํธ๋ชจ๋
- ํ์ ์คํฌ๋ฆฝํธ
- ๊ฐ๋ฐ์๋ถํด๋ฝ
- firebase
- reduce
- ๋ฐ์ํ์คํจ2
- onsnapshot
- ํด๋ก ์ฝ๋ฉ
- styled-components
- sort
- ๋คํฌ๋ชจ๋
- Today
- Total
rigood
[React] ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋๋ ๋ฐฉ๋ฒ 2 ๋ณธ๋ฌธ
[React] ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋๋ ๋ฐฉ๋ฒ 2
rigood 2022. 12. 20. 00:40์ง๋ ๊ธ์์๋ absolute์ opacity๋ฅผ ์ด์ฉํ์ฌ ์ฌ๋ผ์ด๋๋ฅผ ๋ง๋ค์ด ๋ณด์์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ CSS์ translateX ์์ฑ์ ์ด์ฉํ์ฌ ์์ผ๋ก ์ด๋ํ๋ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ์์ฑ์ท์ ๋๋ค.
์ด๋ ๋ฒํผ๊ณผ ํ์ด์ง๋ค์ด์ ๊น์ง ๊ตฌํํ ๋ชจ์ต์ ๋๋ค.

๋ค๋ง ๋ฌธ์ ๋...
๋ง์ง๋ง ์ฌ๋ผ์ด๋์์ Next ๋ฒํผ ํด๋ฆญ ์ ๋งจ ์ฒ์ ์ฌ๋ผ์ด๋๋ก ๊ฑฐ๊พธ๋ก ๋์๊ฐ๋๋ค.
์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋์์ Prev ๋ฒํผ์ ๋๋ฅผ ๋๋ ์ผ์ชฝ์ผ๋ก ์ด๋ํ๋๊ฒ ์๋๋ผ,
์ค๋ฅธ์ชฝ ๋ฐฉํฅ์ผ๋ก ์ด๋ํ์ฌ ์ค๊ฐ์ ์๋ ๋ชจ๋ ์ด๋ฏธ์ง๋ฅผ ๊ฑฐ์ณ ๋ง์ง๋ง ์ฌ๋ผ์ด๋์ ๋๋ฌํ๊ฒ ๋ฉ๋๋ค.

translateX๋ฅผ ์ด์ฉํ์ฌ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๋ง๋ค๊ธฐ
โป CSS๋ styled-components ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
0. ๊ตฌํ ์๋ฆฌ ํ๋์ ๋ณด๊ธฐ


์ด๋ฏธ์ง๋ฅผ ์ผ๋ ฌ๋ก ์ญ ๋์ดํ ๋ค์
translateX๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ด๋์ํค๋ ์๋ฆฌ์ ๋๋ค.
์ค์ ๋ก๋ ์ ํํฌ์ ๋ฒํผ์ด ์๋ ๊ตฌ๊ฐ๋ง ๋ณด์ด๋๋ฐ
์ดํด๋ฅผ ๋๊ธฐ ์ํด overflow:hidden ์์ฑ์ ์ ์ ์ง์๋ณด์์ต๋๋ค.
์ฒซ๋ฒ์งธ ์ด๋ฏธ์ง๋ ์๋ ๊ทธ ์๋ฆฌ์ฌ์ ์ด๋์ํฌ ํ์๊ฐ ์์ผ๋๊น translateX(0)
๋๋ฒ์งธ ์ด๋ฏธ์ง๋ ์ ํํฌ์ ๋ฒํผ์ด ์๋ ๊ตฌ๊ฐ์ ๋๋น๋งํผ ์ผ์ชฝ์ผ๋ก ์ด๋์ํค๋ฉด ๋๊ฒ ์ฃ ?
๊ทธ๋์ translateX(-100%)
์ธ๋ฒ์งธ ์ด๋ฏธ์ง๋ translateX(-200%)
๋ค๋ฒ์งธ ์ด๋ฏธ์ง๋ translateX(-300%)
์ด๋ฐ์์ผ๋ก ์ด๋ฏธ์ง ์์์ ๋ฐ๋ผ ์ด๋ํญ์ ๋๋ ค์, ํ๋ฉด์ ๋ณด์ผ ์ฌ๋ผ์ด๋๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐฉ์์ ๋๋ค.
1. useState๋ก ํ๋ฉด์ ๋ณด์ฌ์ง ์ฌ๋ผ์ด๋ ๋ฒํธ ์ง์
function Slider2() {
const [slideIndex, setSlideIndex] = useState(0);
useState๋ก slideIndex๋ผ๋ state๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋๋ ์ด๋ํญ์ด 0์ด๊ธฐ ๋๋ฌธ์ ์ด๊ธฐ๊ฐ์ 0์ผ๋ก ์ค์ ํด์ฃผ์ธ์.
2. ์ฌ๋ผ์ด๋ ๋์ด, ์ด๋ ๋ฒํผ ์์ฑ
return (
<Container>
<Arrow direction="prev">โ</Arrow>
<Wrapper slideIndex={slideIndex}>
{data.map((character) => (
<Slide key={character.id}>
<Photo
src={process.env.PUBLIC_URL + `/img/slider/${character.img}`}
/>
</Slide>
))}
</Wrapper>
<Arrow direction="next"}>โถ</Arrow>
</Container>
);
}
์ฌ๋ผ์ด๋์ ๋ฒํผ์ ๋ด์ Container ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
Prev, Next ๋ฒํผ๋ ๋ง๋ค์ด์ฃผ๊ณ ,
Wrapper ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ๊ทธ ์์ map์ ํตํด Slide ์ปดํฌ๋ํธ๋ฅผ ๋์ดํด์ฃผ์ธ์.
Arrow ๋ฒํผ ์ปดํฌ๋ํธ์๋ ๋ฒํผ ๋ฐฉํฅ์ props๋ก ์ ๋ฌํด์ฃผ์๊ณ ,
Wrapper ์ปดํฌ๋ํธ์๋ slideIndex๋ฅผ props๋ก ์ ๋ฌํด์ฃผ์ธ์.
translateX๋ฅผ ํตํด ์์ง์ด๋ ๋์์ด ๋ฐ๋ก Wrapper ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์
slideIndex๋ฅผ props๋ก ๋ฐ์ ์์ง์ด๋ ๊ฑฐ๋ฆฌ๋ฅผ ์กฐ์ ํด์ค๊บผ์์.
3. CSS ์์ฑ ์ง์
const Container = styled.div`
// ํฌ๊ธฐ ์ง์
width: 200px;
height: 200px;
// ์ ์ฌ๋ฐฑ, ๊ฐ์ด๋ฐ ์ ๋ ฌ
margin: 100px auto;
// Container ํฌ๊ธฐ๋ฅผ ์ด๊ณผํ๋ ๋ถ๋ถ ์จ๊ธฐ๊ธฐ
overflow: hidden;
// position: absolute์ธ Arrow ์ปดํฌ๋ํธ ๋ฐฐ์น
position: relative;
`;
const Wrapper = styled.div`
// ๋ถ๋ชจ ์์์ธ Container ์ปดํฌ๋ํธ์ ๋์ด์ ๋ง์ถ๊ธฐ
// width๋ ์ง์ ํ์ง ๋ง ๊ฒ!
height: 100%;
// flex๋ฅผ ํตํด Slide ์ผ๋ ฌ๋ก ๋์ด
display: flex;
// ์ด๋ํ ๋ ํธ๋์ง์
์ ์ฉ
transition: all 0.3s ease-in-out;
// props๋ก ๋๊ฒจ๋ฐ์ slideIndex์ ๋ฐ๋ผ x์ถ ์ด๋ํญ ๊ฒฐ์
transform: translateX(${({ slideIndex }) => slideIndex * -100 + "%"});
`;
const Slide = styled.div`
// ๋ถ๋ชจ ์์์ธ Wrapper์ width๊ฐ ์ ํด์ง์ง ์์์ผ๋ฏ๋ก
// ์กฐ๋ถ๋ชจ ์์์ธ Container ์ปดํฌ๋ํธ์ width์ ๋ง์ถฐ์ง
width: 100%;
height: 100%;
// ๋ถ๋ชจ ์์์ ๋๋น์ ์๊ด์์ด ์์ ์ ํฌ๊ธฐ๋ฅผ ์ ์งํ๋ ์์ฑ
flex-shrink: 0;
`;
const Photo = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
`;
const Arrow = styled.div`
// 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: 36px;
height: 36px;
border-radius: 50%;
background-color: pink;
cursor: pointer;
// ๋ด๋ถ ๊ฐ์ด๋ฐ ์ ๋ ฌ
display: flex;
justify-content: center;
align-items: center;
// Prev ๋ฒํผ์ด Slide์ ๊ฐ๋ฆฌ์ง ์๋๋ก z-index ์ง์
z-index: 1;
`;
Container ์ปดํฌ๋ํธ๋ฅผ ํตํด ์ฌ๋ผ์ด๋์ ํฌ๊ธฐ๋ฅผ ์ ํด์ค๋๋ค.
Wrapper ์ปดํฌ๋ํธ๋ ๊ฐ๋ก๋ก ์ญ ๋์ด๋ Slide๋ฅผ ๊ฐ์ธ๋ ์์์ด๊ธฐ ๋๋ฌธ์ width ๊ฐ์ ๋ฐ๋ก ์ง์ ํ์ง ์์ต๋๋ค.
props๋ก ๋๊ฒจ๋ฐ์ slideIndex์ ๋ฐ๋ผ translateX์ ์ด๋ํญ์ ์ค์ ํด์ค๋๋ค.
์ด๋ ๋ฒํผ์ ํ ๋ฒ ํด๋ฆญํ ๋๋ง๋ค, ๋ถ๋ชจ ์์์ธ Container ์ปดํฌ๋ํธ์ width๋งํผ ์ด๋ํด์ผ ํ๊ธฐ ๋๋ฌธ์
๋จ์๋ % ๋ก ์ก์์ค๋๋ค. (% ๋จ์๋ ๋ถ๋ชจ ์์ ๊ธฐ์ค์ ๋๋ค.)
transform: translateX(${({ slideIndex }) => slideIndex * -100 + "%"});
slideIndex๊ฐ 0์ด๋ฉด 0% (์ด๋์ํจ)
slideIndex๊ฐ 1์ด๋ฉด -100% (x์ถ ์ผ์ชฝ์ผ๋ก ๋ถ๋ชจ์์ width๋งํผ ์ด๋)
slideIndex๊ฐ 2์ด๋ฉด -200% (x์ถ ์ผ์ชฝ์ผ๋ก ๋ถ๋ชจ์์ width 2๋ฐฐ๋งํผ ์ด๋)
Slide ์ปดํฌ๋ํธ์๋ flex-shrink: 0 ์์ฑ์ ์ถ๊ฐํด์ค๋๋ค.
์ด ์์ฑ์ด ์์ผ๋ฉด, ๋ถ๋ชจ ์์์ธ Wrapper ์ปดํฌ๋ํธ width์ ๋ง์ถ๊ธฐ ์ํด width๊ฐ ์ค์ด๋ค๊ฒ ๋ฉ๋๋ค.
flex-shrink:0 ์ ์ถ๊ฐํ๋ฉด ๋ถ๋ชจ ์์์ ๋๋น์ ์๊ด์์ด ์์ ์ ํฌ๊ธฐ๋ฅผ ๊ทธ๋๋ก ์ ์งํฉ๋๋ค.
4. Prev, Next ๋ฒํผ ํด๋ฆญ ํจ์ ๋ง๋ค๊ธฐ
const moveToPrevSlide = () => {
setSlideIndex((prev) => (prev === 0 ? data.length - 1 : prev - 1));
};
const moveToNextSlide = () => {
setSlideIndex((prev) => (prev === data.length - 1 ? 0 : prev + 1));
};
<Arrow direction="prev" onClick={moveToPrevSlide}>โ</Arrow>
<Arrow direction="next" onClick={moveToNextSlide}>โถ</Arrow>
Prev ๋ฒํผ์ ๋๋ ์ ๋
๊ธฐ์กด slideIndex๊ฐ 0์ด๋ฉด(์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋์ด๋ฉด) slideIndex๋ฅผ data.length -1(๋ง์ง๋ง ์ฌ๋ผ์ด๋)๋ก ๋ฐ๊ฟ์ค๋๋ค.
(slideIndex๊ฐ 0๋ถํฐ ์์ํ๊ธฐ ๋๋ฌธ์ ๋ง์ง๋ง ์ฌ๋ผ์ด๋์ ๋ฒํธ๋ ๋ฐฐ์ด ๊ธธ์ด-1์ด ๋ฉ๋๋ค.)
๊ทธ ์ธ ๊ฒฝ์ฐ์๋ slideIndex๋ฅผ 1์ฉ ๊ฐ์์ํต๋๋ค.
Next ๋ฒํผ์ ๋๋ ์ ๋
๊ธฐ์กด slideIndex๊ฐ data.length-1์ด๋ฉด(๋ง์ง๋ง ์ฌ๋ผ์ด๋๋ผ๋ฉด) slideIndex๋ฅผ 0์ผ๋ก(์ฒซ๋ฒ์งธ ์ฌ๋ผ์ด๋)๋ก ๋ฐ๊ฟ์ฃผ๊ณ ,
๊ทธ ์ธ ๊ฒฝ์ฐ์๋ slideIndex๋ฅผ 1์ฉ ์ฆ๊ฐ์ํต๋๋ค.
Arrow ์ปดํฌ๋ํธ์ onClick ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๋ฌ์์ค๋๋ค.
5. ํ์ด์ง๋ค์ด์ ๊ตฌํํ๊ธฐ
const moveDot = (index) => {
setSlideIndex(index);
};
<DotContainer>
{data.map((character, index) => (
<Dot
key={character.id}
className={index === slideIndex ? "active" : null}
onClick={() => moveDot(index)}
/>
))}
</DotContainer>
DotContainer๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์์ map์ ํตํด Dot์ ๋์ดํด์ค๋๋ค.
map์ ๋๋ฒ์งธ ์ธ์์ธ index๋ฅผ ์ฌ์ฉํ์ฌ index์ ํ์ฌ slideIndex๊ฐ ์ผ์นํ ๋
active๋ผ๋ ํด๋์ค๋ฅผ ๋ถ์ฌํด์ค๋๋ค.
Dot ํด๋ฆญ ์ index๋ฅผ ์ธ์๋ก ๋ฐ์ slideIndex๋ฅผ ๋ณ๊ฒฝํด์ค๋๋ค.
Wrapper์ props๋ก ์ ๋ฌ๋ slideIndex์ ๋ฐ๋ผ x์ถ ์ด๋ํญ์ด ๋ฌ๋ผ์ง๊ณ ,
slideIndex์ ๋ง๋ ์ด๋ฏธ์ง๊ฐ ํ๋ฉด์ ํ์๋ฉ๋๋ค.
const DotContainer = styled.div`
position: absolute;
bottom: 10px;
left: 0;
right: 0;
margin: 0 auto;
width: 100px;
display: flex;
justify-content: space-between;
`;
const Dot = styled.div`
width: 10px;
height: 10px;
border-radius: 50%;
background-color: pink;
cursor: pointer;
&.active {
background-color: skyblue;
}
Dot ์ปดํฌ๋ํธ์์๋ active ํด๋์ค๋ฅผ ํตํด
ํ์ฌ ํด๋ฆญ๋ Dot๋ง ๋ฐฐ๊ฒฝ์์ ๋ค๋ฅด๊ฒ ์ค์ ํด์ค๋๋ค.
์ ์ฒด ์ฝ๋
import { useState } from "react";
import styled from "styled-components";
import data from "./data";
function Slider2() {
const [slideIndex, setSlideIndex] = useState(0);
const moveToPrevSlide = () => {
setSlideIndex((prev) => (prev === 0 ? data.length - 1 : prev - 1));
};
const moveToNextSlide = () => {
setSlideIndex((prev) => (prev === data.length - 1 ? 0 : prev + 1));
};
const moveDot = (index) => {
setSlideIndex(index);
};
return (
<Container>
<Arrow direction="prev" onClick={moveToPrevSlide}>
โ
</Arrow>
<Wrapper slideIndex={slideIndex}>
{data.map((character) => (
<Slide key={character.id}>
<Photo
src={process.env.PUBLIC_URL + `/img/slider/${character.img}`}
/>
</Slide>
))}
</Wrapper>
<Arrow direction="next" onClick={moveToNextSlide}>
โถ
</Arrow>
<DotContainer>
{data.map((character, index) => (
<Dot
key={character.id}
className={index === slideIndex ? "active" : null}
onClick={() => moveDot(index)}
/>
))}
</DotContainer>
</Container>
);
}
export default Slider2;
const Container = styled.div`
width: 200px;
height: 200px;
margin: 100px auto;
overflow: hidden;
position: relative;
`;
const Wrapper = styled.div`
height: 100%;
display: flex;
transition: all 0.3s ease-in-out;
transform: translateX(${({ slideIndex }) => slideIndex * -100 + "%"});
`;
const Slide = styled.div`
width: 100%;
height: 100%;
flex-shrink: 0;
`;
const Photo = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
`;
const Arrow = styled.div`
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
left: ${({ direction }) => direction === "prev" && "0px"};
right: ${({ direction }) => direction === "next" && "0px"};
width: 36px;
height: 36px;
border-radius: 50%;
background-color: pink;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
z-index: 1;
`;
const DotContainer = styled.div`
position: absolute;
bottom: 10px;
left: 0;
right: 0;
margin: 0 auto;
width: 100px;
display: flex;
justify-content: space-between;
`;
const Dot = styled.div`
width: 10px;
height: 10px;
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;