React를 이용해서 Tublbug Page를 제작할 계획
1단계 ) Navigation + Image Slider - 오늘 구현
2단계 ) Grid + 반응형
[ 결과물 ]
- 퍼블리싱이 목적이기 때문에 틀만 제작
- 구조는 아래와 같다
(Navbar.js)
import React from 'react' import styled from 'styled-components'; const NavBarTamplate = styled.div` display: flex; justify-content: space-between; align-items: center; width: 1035px; `; ... function Navbar() { return ( <> <NavBarTamplate> <NavBarLeft> <Menu> <i class="fas fa-bars"></i> </Menu> <Text1>프로젝트 둘러보기</Text1> <Text2>프로젝트 올리기</Text2> </NavBarLeft> <Logo src="/imgs/logo.png"></Logo> <NavbarRight> <Info> <ImgSearch src="https://tumblbug.com/wpa/e5aa161342e919b420a40fc6e34cce08.png"></ImgSearch> <Text3>로그인 / 회원가입</Text3> <ImgUser src="https://tumblbug-assets.imgix.net/assets/user-account.png?s=08b1f9ecf24209994ac9b81900936c0e"></ImgUser> </Info> </NavbarRight> </NavBarTamplate> </> ) } export default Navbar
: 내비게이션을 왼쪽메뉴 / 로고 / 오른쪽메뉴 3가지로 구성하고
flex disply에 space-between으로 레이아웃 배치
[ 결과물 ]
[ 원리 ]
- 정해진 슬라이더 부분에 이미지와 텍스트 부분을 따로 지정
- 이미지 or 텍스트는 각각 하나의 크기 이상은 모두 overflow:hidden
- 버튼 클릭시 각(이미지, 텍스트) 크기만큼(width:100%) x축으로 transform
- 파일 구조 : Slider.js 내부에 Image.js와 SlideText.js존재
- setInterval로 2초마다 자동으로 넘어감
[ 코드 ]
(App.js)
... function App() { return ( <> <NavTemplate> <Navbar></Navbar> </NavTemplate> <Template> <Slider object={object}></Slider> </Template> </> ); }
: Slider에게 이미지 리스트인 object를 props로 전달
(Slider.js)
function Slider({object}) { const [chIdx,setChIdx] = useState(0); const onClickLeftButton = ()=>{ if(chIdx != 0){ setChIdx(chIdx-1); }else{ setChIdx(object.length-1) } } const onClickRightButton = ()=>{ if(chIdx != object.length-1){ setChIdx(chIdx+1); }else{ setChIdx(0); } } return ( <SliderTemplate> <ImageTemplate> { object.map((obj,idx)=>( <Image key={`Image-${idx}`} chIdx={chIdx} obj={obj} </Image> )) } </ImageTemplate> <SideTextTemplate> { object.map((obj,idx)=>( <SideText chIdx={chIdx} obj={obj} key={`SideText-${idx}`} onClickLeftButton={onClickLeftButton} onClickRightButton={onClickRightButton}> </SideText> )) } </SideTextTemplate> </SliderTemplate> )
- chIdx : 현재 사용자가 선택한 인덱스를 가리키는 State
- Slider안에 이미지를 먼저 쭉~ 나열하고, 그다음 텍스트를 쭉~나열
- 어차피 ImageTemplate와 SiderTextTemplate에서 overflow:hidden이라서 하나만 보이게 할 것
(Image.js)
import React from 'react' import styled, {css} from 'styled-components'; const Img = styled.img` width:100%; cursor: pointer; ${props => css` transform: translate(-${(props.chIdx)*100}%,0px); transition: 0.5s; ` } `; function Image({obj, chIdx}) { return ( <> <Img chIdx={chIdx} src={obj.img}></Img> </> ); } export default Image
: 선택된 인덱스 * width의 100%만큼 움직이게 한다. (중요)
(SideText.js)
import React from 'react' import styled, {css} from 'styled-components'; const SideWrap = styled.div` background-color: #222222; display: flex; z-index: 2; flex-direction: column; justify-content: center; box-sizing: border-box; transition-property: background-color; transition-duration: 0.8s; ${props => css` transform: translate(-${(props.chIdx)*100}%,0px); ` } ${props => props.chIdx == 1? css`background-color: #378eb5;` : (props.chIdx == 2? css`background-color: #d77847;` : (props.chIdx == 3? css`background-color: #f4adac;` : (props.chIdx == 4? css`background-color: #2fa789;` : css`background-color: #222222;`))) }; `; ... function SideText({chIdx, obj, onClickLeftButton, onClickRightButton}) { return ( <> <SideWrap chIdx={chIdx}> <TextBox> <MainText> <MainTxt1>{obj.mainTxt1}</MainTxt1> <MainTxt2>{obj.mainTxt2}</MainTxt2> </MainText> <SubText>{obj.subTxt}</SubText> </TextBox> <ButtonAndPage> <ButtonBox> <ButtonLeft onClick={onClickLeftButton}> <i className="fas fa-chevron-left"></i> </ButtonLeft> <ButtonRight onClick={onClickRightButton}> <i className="fas fa-chevron-right"></i> </ButtonRight> </ButtonBox> <PageCnt>{obj.idx+1} / 5</PageCnt> </ButtonAndPage> </SideWrap> </> ) } export default SideText
- 전체를 책임지는 SideWrap에서 인덱스마다 조건부 css를 중첩으로 구현
- 그 외에 특별한 특이사항은 없다
react에서 setInterval을 사용하기는 바닐라JS때와 달리 조금 까다롭다.
왜냐하면 hooks에서 생명주기인 useEffect에 clearInterval을 지정해줘야 하며,
useRef로 사용해야 하기 때문이다
(Slider.js)
... const tmp = useRef(); const onAutoIncrease = () => { if(chIdx != object.length-1){ setChIdx(chIdx+1); }else{ setChIdx(0); } } /* react에서 Interval 사용시 아래와 같이 사용해야함!! */ useEffect(() => { tmp.current = onAutoIncrease; }); useEffect(()=>{ function tick() { tmp.current(); } let id = setInterval(tick, 2000); return () => clearInterval(id); },[]).
- 사용방법
1) useRef()로 변수를 생성
2) useEffect로 해당 ref에 콜백함수 연결
3) 컴포넌트가 처음 마운트 되었을 때 ref를 사용한 콜백함수와 시간으로 변수에 setInterval을 할당!
4) return되었을 때 setInterval을 clear!
- useEffect에서 return은 컴포넌트가 소멸될 때를 의미하기 때문에,
Interval을 clear해줘야 쌓이지 않는다
[ Image / Text 통합시 ]
처음에 Image와 Text를 분리하지 않고 하나의 컴포넌트로 했더니
이미지 슬라이더가 넘어갈 때 Text크기만큼의 공백이 보였음그래서 본 글에서는 Image와 Text 컴포넌트를 따로 분리함
[ 일부 속성에 Transition 적용 ]
transition-property: background-color; transition-duration: 0.8s;
: 당연하긴 했지만 트랜지션을 한번에 주는것에 익숙해져 까먹고 있었음;