브런치 클론 프로젝트에서 나의 주요 담당 파트는 메인 페이지였다.
메인 페이지에는 UI적으로 구현할 부분이 많았는데,
그 중에서도 슬라이더 부분에 가장 많은 공을 들여 구현하였다.
1차 성장)
하드 코딩으로 어찌저찌 모양은 만듦.
그치만 코드가 너무 길어서 내가 봐도 무슨 코든지 알아볼 수 없없음
2차 성장)
모양이 같은 슬라이더를 모아 하나의 컴포넌트로 만듦.
총 4개의 구성이라 4개의 컴포넌트가 생김. 이제는 코드를 알아볼 수는 있음
3차 성장)
디자인 구성 상관없이 하나의 컴포넌트로 전체를 커버.
html은 매우 간결해졌으나, 디자인의 한계로 css는 조금 복잡
슬라이더 구현
보여지는 부분 * 슬라이더 개수
로 계산하여 너비 설정을 하고, grid를 활용해 각각의 콘텐츠(컴포넌트)의 위치를 설정slide position
과 activeBtn
을 설정하고 state 값을 변화시키는 함수를 제작하여 이동 위치 설정transform
을 활용하여 이동기능까지 구현 + transition
으로 스피드 조절좌우로 이동할 때 어디에 마이너스 값을 줘야하는지 너무 헷갈려서 한참 헤맸다..
버튼 구현
activeBtn
의 경우 코드는 잘 작성했으나 작동을 하지 않아 한참 고민했는데, Number인지 String인지의 차이로 값이 제대로 저장되지 않아서 였다!render
내에 슬라이드가 처음인지와 끝인지를 판별하는 테스트를 추가하여 boolean값에 따라 좌우버튼이 숨겨지거나 보이도록 구현[React]
import React from 'react';
import SlideContent from './SlideContent';
import './LikedContents.scss';
class LikedContents extends React.Component {
constructor() {
super();
this.state = {
slidePosition: 0,
activeBtn: '1',
};
}
slideSize = 970;
hiddenSlideLength = 3;
handlePrevBtn = e => {
const { slidePosition, activeBtn } = this.state;
const { slideSize } = this;
if (slidePosition >= 0) {
this.setState({ slidePosition: 0 });
} else {
this.setState({
slidePosition: slidePosition + slideSize,
activeBtn: (Number(activeBtn) - 1).toString(),
});
}
};
handleNextBtn = e => {
const { slidePosition, activeBtn } = this.state;
const { slideSize, hiddenSlideLength } = this;
if (slidePosition <= slideSize * -1 * 3) {
this.setState({ slidePosition: slideSize * -1 * hiddenSlideLength });
} else {
this.setState({
slidePosition: slidePosition - slideSize,
activeBtn: (Number(activeBtn) + 1).toString(),
});
}
};
moveToSlide = e => {
const { slideSize } = this;
const { value } = e.target;
this.setState({
slidePosition: value * slideSize * -1 + slideSize,
activeBtn: value.toString(),
});
};
render() {
const { slidePosition, activeBtn } = this.state;
const { slideSize, hiddenSlideLength } = this;
const { slideContents } = this.props;
const isSlideEnd = slidePosition === slideSize * -1 * hiddenSlideLength;
const isSlideStart = slidePosition === 0;
return (
<section className="likedContents">
<div className="articleWrapper">
<div
className="slideDisplay"
style={{
transform: `translateX(${slidePosition}px)`,
}}
>
<div className="slideArticles">
{slideContents.map((content, idx) => {
return <SlideContent key={idx} slideContent={content} />;
})}
</div>
</div>
<button
className={isSlideStart ? 'prevBtn hidden' : 'prevBtn'}
onClick={this.handlePrevBtn}
>
<i class="fas fa-chevron-left" />
</button>
<button
className={isSlideEnd ? 'nextBtn hidden' : 'nextBtn'}
onClick={this.handleNextBtn}
>
<i class="fas fa-chevron-right" />
</button>
</div>
<ul className="contentsOrder">
{CONTENT_ORDER_DATA.map((data, idx) => {
return (
<li
key={idx}
value={data.value}
onClick={this.moveToSlide}
className={
activeBtn === `${data.value}` ? 'numBtn active' : 'numBtn'
}
>
{data.text}
</li>
);
})}
</ul>
</section>
);
}
}
const CONTENT_ORDER_DATA = [
{ value: '1', text: '01' },
{ value: '2', text: '02' },
{ value: '3', text: '03' },
{ value: '4', text: '04' },
];
export default LikedContents;
[SCSS]
@import '../../../styles/variables.scss';
.likedContents {
margin-top: 60px;
.articleWrapper {
position: relative;
.slideDisplay {
width: 970px;
height: 500px;
margin: 0 auto;
transition: all 400ms ease-in-out;
.slideArticles {
@include grid(grid, repeat(24, 1fr), 0, repeat(6, 1fr));
width: calc(970px * 4);
height: 100%;
div:nth-of-type(1) {
grid-column: 1 / 4;
grid-row: 1 / 7;
}
div:nth-of-type(2) {
grid-column: 4 / 7;
grid-row: 1 / 4;
}
div:nth-of-type(3) {
grid-column: 4 / 7;
grid-row: 4 / 7;
}
div:nth-of-type(4) {
grid-column: 7 / 9;
grid-row: 1 / 7;
}
div:nth-of-type(5) {
grid-column: 9 / 11;
grid-row: 1 / 7;
}
div:nth-of-type(6) {
grid-column: 11 / 13;
grid-row: 1 / 7;
}
div:nth-of-type(7) {
grid-column: 13 / 16;
grid-row: 1 / 4;
}
div:nth-of-type(8) {
grid-column: 13 / 16;
grid-row: 4 / 7;
}
div:nth-of-type(9) {
grid-column: 16 / 19;
grid-row: 1 / 4;
}
div:nth-of-type(10) {
grid-column: 16 / 19;
grid-row: 4 / 7;
}
div:nth-of-type(11) {
grid-column: 19 / 25;
grid-row: 1 / 5;
}
div:nth-of-type(12) {
grid-column: 19 / 21;
grid-row: 5 / 7;
}
div:nth-of-type(13) {
grid-column: 21 / 23;
grid-row: 5 / 7;
}
div:nth-of-type(14) {
grid-column: 23 / 25;
grid-row: 5 / 7;
}
.slideContent {
@include flex(flex, column, center, center);
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.articleText,
.articleAuthor {
width: 250px;
color: white;
text-align: center;
line-height: 30px;
z-index: 10;
cursor: pointer;
}
.articleText {
@include themeFont;
font-size: 28px;
word-break: keep-all;
}
.articleAuthor {
margin-top: 15px;
font-size: 15px;
}
.articleImage,
.cover {
position: absolute;
width: 100%;
height: 100%;
}
.articleImage {
background-size: cover;
background-position: center;
transform: scale(1);
transition: transform 400ms ease-in-out;
z-index: 1;
}
&:hover .articleImage {
transform: scale(1.1);
}
.cover {
background-color: rgba(0, 0, 0, 0.2);
transition: background-color 400ms ease-in-out;
z-index: 5;
}
&:hover .cover {
background-color: rgba(0, 0, 0, 0.4);
cursor: pointer;
}
}
}
}
.prevBtn,
.nextBtn {
position: absolute;
top: 210px;
width: 100px;
height: 100px;
border: none;
border-radius: 100%;
color: grey;
background-color: white;
opacity: 60%;
font-size: 40px;
font-weight: 100;
cursor: pointer;
&.hidden {
display: none;
}
}
.prevBtn {
left: 100px;
}
.nextBtn {
right: 100px;
}
}
.contentsOrder {
@include flex(flex, row, center);
margin-top: 25px;
.numBtn {
margin-left: 15px;
padding-bottom: 2px;
color: rgb(204, 204, 204);
font-size: 12px;
cursor: pointer;
&.active {
color: black;
border-bottom: 1px solid black;
font-weight: bold;
}
}
}
}