리액트 프로젝트 만들기 - 2
Creatin HeroSection
로고, 메뉴 바로 다음에 표시되는 항목으로 이 사이트가 어떤 사이트인지를 간결하게 표시해주는 항목이다.
src에 HeroSection
폴더를 생성하고 index
와 HeroElements
파일을 생성한다.
index
/* eslint-disable react/jsx-no-undef */
import React from 'react'
const HeroSection = () => {
return (
<HeroContainer>
<HeroBg>
<VideoBg autoPlay loop muted src={Video} type='video/mp4'/>
</HeroBg>
</HeroContainer>
)
}
export default HeroSection
Styling HeroSection
HeroContainer
: HeroSection 외부 레이아웃
HeroBg
: HeroSection의 배경화면
VideoBg
: 배경화면 컴포넌트로 영상 파일을 불러와서 반복 재생할 것이다.
HeroElements
export const HeroContainer = styled.div`
background: #0c0c0c;
display: flex;
justify-content:center;
align-items: center;
padding: 0 30px;
height: 800px;
position: relative;
z-index: 1;
`
export const HeroBg = styled.div`
position: absolute;
top:0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
`
export const VideoBg = styled.video`
width:100%;
height:100%;
-o-object-fit: cover;
object-fit: cover;
background:#232a34;
`
index에 import해준다.
src/components/HeroSection/index.js
/* eslint-disable react/jsx-no-undef */
import React from 'react'
import { HeroBg, HeroContainer, VideoBg } from './HeroElements'
import Video from '../../videos,mp4';
const HeroSection = () => {
return (
<HeroContainer>
<HeroBg>
<VideoBg autoPlay loop muted src={Video} type='video/mp4'/>
</HeroBg>
</HeroContainer>
)
}
export default HeroSection
```js
`pages`의 `index`내부에 `HeroSection`을 추가한다.
`pages/index.js`
import React,{useState} from 'react'
import HeroSection from '../components/HeroSection'
import Navbar from '../components/Navbar'
import Sidebar from '../components/Sidebar'
const Home = () => {
const [isOpen,setIsOpen] = useState(false)
const toggle = () => {
setIsOpen(!isOpen)
}
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle}/>
<Navbar toggle={toggle}/>
<HeroSection/>
</>
)
}
export default Home
배경화면 영상으로 선택한 파일은 아직 추가되지 않았다. 따라서 영상 파일을 가져와야하는데, 참고한 사이트는 https://www.pexels.com/videos이다.
검색창에 본인이 원하는 키워드를 검색해서 영상을 다운로드 받고, 그 파일을 가져오면 된다. 작성자의 경우는 data라고 검색해서 맨 첫번째에 존재하는 영상을 가져왔다.
해당 파일을 videos
에 추가하고 실행해보면
현재상황
이렇게 메인 화면에 영상파일이 배경화면으로 사용된다.
HeroSection에 들어갈 문구와 버튼을 작성한다.
<HeroContainer>
<HeroBg>
<VideoBg autoPlay loop muted src={Video} type='video/mp4'/>
</HeroBg>
<HeroContent>
<HeroH1>Virtual Banking Made Easy</HeroH1>
<HeroP>
Sign up for a new account today and receive $250 in credit towards your
next payments.
</HeroP>
<HeroBtnWrapper>
<button to='signup'>
Get started {hover ? <ArrowForward/> : <ArrowRight/>}
</button>
</HeroBtnWrapper>
</HeroContent>
</HeroContainer>
HeroContent
: HeroSection 내부에 들어갈 문장과 버튼을 담은 레이아웃
HeroH1
: 제목
HeroP
: 부연설명
HeroBtnWrapper
: 버튼 컴포넌트를 담은 레이아웃
ArrowForward
: react-icon 에서 가져온 MdArrowForward 아이콘
ArrowRight
: react-icon 에서 가져온 MDKeyboardArrowRight 아이콘
HeroElements.js
export const HeroContent = styled.div`
z-index: 3;
max-width: 1200px;
position: absolute;
padding: 8px 24px;
display: flex;
flex-direction: column;
align-items: center;
`
export const HeroH1 = styled.h1`
color: #fff;
font-size: 48px;
text-align: center;
@media screen and (max-width: 768px){
font-size: 40px;
}
@media screen and (max-width: 480px){
font-size: 32px;
}
`
export const HeroP = styled.p`
margin-top: 24px;
color: #fff;
font-size: 24px;
text-align: center;
max-width: 600px;
@media screen and (max-width: 768px){
font-size: 24px;
}
@media screen and (max-width: 480px){
font-size: 18px;
}
`
export const HeroBtnWrapper = styled.div`
margin-top:32px;
display: flex;
flex-direction: column;
align-items: center;
`
export const ArrowForward = styled(MdArrowForward)`
margin-left: 8px;
font-size: 20px;
`
export const ArrowRight = styled(MdKeyboardArrowRight)`
margin-left: 8px;
font-size: 20px;
`
index에 import 시킨다.
HeroBtnWrapper
에 있는 Button
엘리먼트에 마우스를 올리면 ArrowForward
가 되었다가 커서가 영역을 벗어나면 ArrowRight
가 된다. 따라서 Button
컴포넌트의 hover 상태를 관리하기 위해 useState를 사용했다.
HeroSection/index.js
const [hover , setHover] = useState(false);
const onHover = () => {
setHover(!hover);
}
...
<HeroBtnWrapper>
<Button to='signup' onMouseEnter={onHover} onMouseLeave={onHover}>
Get started {hover ? <ArrowForward/> : <ArrowRight/>}
</Button>
</HeroBtnWrapper>
Creating Button Element
Button에 마우스를 올리면 hover값이 변경되고, 마우스가 Button에서 벗어나면 다시 hover 값이 변경된다. 아직 Button 컴포넌트를 작성하지 않았으므로 Button엘리먼트를 작성한다.
다른 컴포넌트에도 사용할수 있도록 component 폴터 안에서 바로 생성한다. ButtonElement.js
import styled from "styled-components";
import { Link } from "react-scroll";
export const Button = styled(Link)`
border-radius: 50px;
background: ${({ primary }) => (primary ? "#01BF71" : "#010606")};
white-space: nowrap;
padding: ${({big}) => (big ? "14px 48px" : "12px 30px")};
color: ${({dark}) => (dark ? "#010606" : "#fff")};
font-size: ${({fontBig}) => (fontBig ? "20px" : "16px")};
outline: none;
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
&:hover {
transition: all 0.2s ease-in-out;
background: ${({primary}) => (primary ? "#fff" : "#01BF71")};
}
`;
`
Button을 재사용할때 primary, big, dark, fontBig, 같은 속성을 부여하여 각기 다른 스타일링이 적용된 Button 컴포넌트를 렌더링 할수 있다.
현재 상황
마우스 커서를 올리면 배경과 문구 우측의 아이콘의 모양이 변경된다. 너비길이에 따라 문구 텍스트 크기가 변경되는 모습이다.
Button에 설정을 추가하면 스타일이 변경된다. 예를 들어서 primary와 dark 속성을 넣어서 실행하면
<Button
to='signup'
onMouseEnter={onHover}
onMouseLeave={onHover}
primary='true'
dark='true'
>
Get started {hover ? <ArrowForward/> : <ArrowRight/>}
</Button>
버튼의 배경과 폰트의 스타일이 변경된 모습을 볼수 있다.
※ HeroContainer
의 배경화면을 linear-gradient
를 사용해서 좀더 어두운 느낌으로 만들었다. :before
항목에 background 속성을 추가했다.
background: linear-gradient(180deg, rgba(0,0,0,0.2) %0,
rgba(0,0,0,0.6) 100%), linear-gradient(180deg, rgba(0,0,0,0.2) 0%, transparent 100%);
z-index: 2;
위의 스타일을 적용하면 어떻게 되는지 궁금하면 아래의 링크를 참조하자.
codepen 실습자료
Creating Reusable Info Section
사이트에서 제공하는 정보를 표시하는 컴포넌트를 제작한다.
/* eslint-disable react/jsx-no-undef */
import React from 'react'
const InfoSection = () => {
return (
<>
<InfoContainer id={id}>
<InfoWrapper>
<InfoRow>
<Column1>
<TextWrapper>
<TopLine>TopLine</TopLine>
<Heading>Heding</Heading>
<Subtitle>Subtitle</Subtitle>
<BtnWrap>
<Button to="home"></Button>
</BtnWrap>
</TextWrapper>
</Column1>
</InfoRow>
</InfoWrapper>
</InfoContainer>
</>
)
}
export default InfoSection
InfoContainer
: 외부 레이아웃, id 값을 지정한다.
InfoWrapper
: Info 컴포넌트 를 감싸는 Wrapper
InfoRow
: Info Column 항목들을 감싸는 레이아웃
Column
: 단일 Info Column 항목내 텍스트 전체를 감싸는 레이아웃
TextWrapper
: TopLine
, Heading
,Subtitle
로 구성됨
TopLine
: 글머리
Heading
: 제목
Subtitle
: 설명
BtnWrap
내부에 있는 Button
은 이전에 제작한 Button
컴포넌트를 사용할 것이다.
Styling Info section
import styled from 'styled-components'
export const InfoContainer = styled.div`
color: #fff;
background: ${({lightBg}) => (lightBg ? '#f9f9f9' : '#010606')};
@media screen and (max-width: 768px) {
padding: 100px 0;
}
`
export const InfoWrapper = styled.div`
display: grid;
z-index: 1;
height: 860px;
width: 100%;
max-width: 1100px;
margin-right: auto;
margin-left: auto;
padding: 0 24px;
justify-content: center;
`
export const InfoRow = styled.div`
display: grid;
grid-auto-columns: minmax(auto, 1fr);
align-items: center;
grid-template-areas: ${({imgStart}) => (imgStart ? `'col2 col1`: `'col1 col2'`)};
@media screen and (max-width: 768px){
grid-template-areas: ${({imgStart}) => (imgStart ? `'col1' 'col2'` : `'col1 col1' 'col2 col2'` )}
}
`
export const Column1 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col1;
`
export const Column2 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col2;
`
export const TextWrapper = styled.div`
max-width: 540px;
padding-top: 0;
padding-bottom: 60px;
`
export const TopLine = styled.p`
color: #01bf71;
font-size: 16px;
line-height: 16px;
font-weight: 700;
letter-spacing: 1.4px;
text-transform: uppercase;
margin-bottom: 16px;
`
export const Heading = styled.h1`
margin-bottom: 24px;
font-size: 48px;
line-height: 1.1;
font-weight: 600;
color: ${({lightText}) => (lightText ? '#f7f8fa' : '#010606')};
@media screen and (max-width: 480px) {
font-size: 32px;
}
`
export const Subtitle = styled.p`
max-width: 440px;
margin-bottom: 35px;
font-size: 18px;
line-height: 24px;
color: ${({darkText}) => (darkText ? '#010606' : '#fff')};
`
export const BtnWrap = styled.div`
display:flex;
justify-content: flex-start;
`
export const ImgWrap = styled.div`
max-width: 555px;
height: 100%;
`
export const Img = styled.img`
width: 100%auto;
margin: 0 0 10px 0;
padding-right: 0;
`
import styled from 'styled-components'
export const InfoContainer = styled.div`
color: #fff;
background: ${({lightBg}) => (lightBg ? '#f9f9f9' : '#010606')};
@media screen and (max-width: 768px) {
padding: 100px 0;
}
`
export const InfoWrapper = styled.div`
display: grid;
z-index: 1;
height: 860px;
width: 100%;
max-width: 1100px;
margin-right: auto;
margin-left: auto;
padding: 0 24px;
justify-content: center;
`
export const InfoRow = styled.div`
display: grid;
grid-auto-columns: minmax(auto, 1fr);
align-items: center;
grid-template-areas: ${({imgStart}) => (imgStart ? `'col2 col1`: `'col1 col2'`)};
@media screen and (max-width: 768px){
grid-template-areas: ${({imgStart}) => (imgStart ? `'col1' 'col2'` : `'col1 col1' 'col2 col2'` )}
}
`
export const Column1 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col1;
`
export const Column2 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col2;
`
export const TextWrapper = styled.div`
max-width: 540px;
padding-top: 0;
padding-bottom: 60px;
`
export const TopLine = styled.p`
color: #01bf71;
font-size: 16px;
line-height: 16px;
font-weight: 700;
letter-spacing: 1.4px;
text-transform: uppercase;
margin-bottom: 16px;
`
export const Heading = styled.h1`
margin-bottom: 24px;
font-size: 48px;
line-height: 1.1;
font-weight: 600;
color: ${({lightText}) => (lightText ? '#f7f8fa' : '#010606')};
@media screen and (max-width: 480px) {
font-size: 32px;
}
`
export const Subtitle = styled.p`
max-width: 440px;
margin-bottom: 35px;
font-size: 18px;
line-height: 24px;
color: ${({darkText}) => (darkText ? '#010606' : '#fff')};
`
export const BtnWrap = styled.div`
display:flex;
justify-content: flex-start;
`
export const ImgWrap = styled.div`
max-width: 555px;
height: 100%;
`
export const Img = styled.img`
width: 100%auto;
margin: 0 0 10px 0;
padding-right: 0;
`
현재 상황
이렇게 글머리,제목, 설명, 버튼이 구현되어있지만 아직 정보의 내용같은 것은 추가하지 않았다. 각 Column
별로 해당 컴포넌트에 내용을 하나한 넣는 것보다 Data 파일을 따로 만들어서 삽입한다.
Adding Data to Info Section
Info Section 을 구성하는 컴포넌트의 구성이 끝났지만 데이터를 추가하지 않아서 제대로 나타나지 않은 모습이었다. 각각 부분에 들어갈 텍스트 내용을 하나하나 바로 직접적으로 InfoElements
에 작성하면 너무 길어질 수 있기 때문에, 텍스트 내용과, 이미지, 버튼 배경화면의 속성값을 작성해서 불러와 사용할수 있는 DATA
컴포넌트를 사용할 것이다.
export const homeObjOne = {
id: 'about',
lightBg: false,
lightText: true,
lightTextDesc: true,
topLine: 'Premium Bank',
headline: 'Unlimited Transactions with zero fees',
description: 'Get access to out exclusive app that allows you to send unlimited transactions without getting charged any fees.',
buttonLabel:'Get started',
imgStart: false,
img: require('../../images/svg-1.svg'),
alt:'Car',
dark: true,
primary: true,
dartText: false
}
이런식으로 해당 Column
의 id는 무엇인지, 텍스트, 배경의 속성은 어떻게 할것인지, 제목, 부제, 설명, 버튼과 이미지 속성값을 작성해서 InfoElements
로 보내는 방식이다.
이미지는 https://undraw.co/illustrations 이곳으로 들어가서 svg 이미지를 가져올 것이다.
다운로드 받은 이미지는 프로젝트에서 images로 옮겨준다. 그리고 img 설정문구를 저렇게만 작성하면 이미지가 나타나지 않을것이다. 뒤에 .default
를 붙여줘야하는데, 이는 require만 붙이면 이미지가 아닌 객체가 return 되기 때문에 default를 붙이면서 이미지 자체를 불러올 수 있게된다.
자, 이제 작성한 Data
컴포넌트를 .InfoSection/index
로 불러와서 해당 속성들의 props를 가져오는것이다.
/* eslint-disable react/jsx-no-undef */
import React from 'react'
import { Button } from '../ButtonElement'
import { BtnWrap, Column1, Column2, Heading, Img, ImgWrap, InfoContainer, InfoRow, InfoWrapper, Subtitle, TextWrapper, TopLine} from './InfoElements';
const InfoSection = ({lightBg, id, imgStart, topLine, lightText, headline, darkText, description, buttonLabel, img,alt, primary, dark}) => {
return (
<>
<InfoContainer lightBg={lightBg} id={id}>
<InfoWrapper>
<InfoRow imgStart={imgStart}>
<Column1>
<TextWrapper>
<TopLine>{topLine}</TopLine>
<Heading lightText={lightText}>{headline}</Heading>
<Subtitle darkText={darkText}>{description}</Subtitle>
<BtnWrap>
<Button to="home"
primary={primary}
dark={dark}>{buttonLabel}</Button>
</BtnWrap>
</TextWrapper>
</Column1>
<Column2>
<ImgWrap>
<Img src={img} alt={alt}/>
</ImgWrap>
</Column2>
</InfoRow>
</InfoWrapper>
</InfoContainer>
</>
)
}
export default InfoSection
현재 상황
index
에 제목, 부제, 설명을 하나하나 작성하지않고, 다른 컴포넌트에 작성해서 불러오는 방식으로 코드를 작성하니까 좀더 깔끔한 느낌이다. 또한 이런방식으로 각기 다른 속성을 불러와서 배경화면이나 버튼의 속성을 변경할수 있게된다.
Adding Smooth Scroll to InfoSection Buttons
부드러운 스크롤 움직임을 위해서 설정을 추가한다.
src/components/InfoSection/index
에서 Button
컴포넌트를 수정한다.
<Button to="home"
primary={primary}
dark={dark}
smooth={true}
duration={500}
spy={true}
exact="true"
offset={-80}
>{buttonLabel}</Button>
InfoSection 에 항목을 더 추가하고 싶으면 DATA 컴포넌트에 추가로 항목을 작성하면 된다.
export const homeObjOne = {
id: 'about',
lightBg: false,
lightText: true,
lightTextDesc: true,
topLine: 'Premium Bank',
headline: 'Unlimited Transaztions with zero fees',
description: 'Get access to out exclusive app that allows you to send unlimited transactions without getting charged any fees.',
buttonLabel:'Get started',
imgStart: false,
img: require('../../images/svg-1.svg').default,
alt:'Car',
dark: true,
primary: true
};
export const homeObjTwo = {
id: 'discover',
lightBg: true,
lightText: false,
lightTextDesc: false,
topLine: 'Unlimited Access',
headline: 'Login to your account at any time',
description: 'Get access to out exclusive app that allows you to send unlimited transactions without getting charged any fees.',
buttonLabel:'Learn More',
imgStart: true,
img: require('../../images/svg-2.svg').default,
alt:'man looking papaer',
dark: false,
primary: false
};
export const homeObjThree = {
id: 'signup',
lightBg: true,
lightText: false,
lightTextDesc: false,
topLine: 'Join our Team',
headline: 'Too easy for Creating account',
description: "Get everything setup and ready in under 10minutes. All you need to do is add your information and you're ready to go",
buttonLabel:'Start now',
imgStart: false,
img: require('../../images/svg-3.svg').default,
alt:'Car',
dark: false,
primary: false
};
이렇게 설정해놓으면 pages/index
에서 간단하게 작성이 가능하다.
import React,{useState} from 'react'
import HeroSection from '../components/HeroSection'
import InfoSection from '../components/InfoSection'
import { homeObjOne, homeObjThree, homeObjTwo } from '../components/InfoSection/Data'
import Navbar from '../components/Navbar'
import Sidebar from '../components/Sidebar'
const Home = () => {
const [isOpen,setIsOpen] = useState(false)
const toggle = () => {
setIsOpen(!isOpen)
}
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle}/>
<Navbar toggle={toggle}/>
<HeroSection/>
<InfoSection {...homeObjOne}/>
<InfoSection {...homeObjTwo}/>
<InfoSection {...homeObjThree}/>
</>
)
}
export default Home
Creating Service Section
사이트가 제공하는 서비스의 내용을 표시하는 섹션을 제작한다.
component
에 Sevices
폴더를 생성하고 index
와 ServicesElements
파일을 생성한다.
index.js
import React from 'react'
const Services = () => {
return (
<ServicesContainer id="services">
<ServicesH1>Our Services</ServicesH1>
<ServicesWrapper>
<ServicesCard>
<ServicesIcon src={Icon1}/>
<ServicesH2>Reduce expenses</ServicesH2>
<ServicesP> we help reduce your fess and increase your overall revenue.</ServicesP>
</ServicesCard>
<ServicesCard>
<ServicesIcon src={Icon2}/>
<ServicesH2>Virtual Offices</ServicesH2>
<ServicesP>
You can access our platform online anywhere in the world.
</ServicesP>
</ServicesCard>
<ServicesCard>
<ServicesIcon src={Icon3}/>
<ServicesH2>Premium Benefits</ServicesH2>
<ServicesP>Unlock our special membership card that returns 5% cash back.</ServicesP>
</ServicesCard>
</ServicesWrapper>
</ServicesContainer>
)
}
export default Services
Styling Sevices Section
세개의 카드 엘리먼트를 가로로 정렬한 모습으로 구성한다.
ServiceElements
import styled from 'styled-components';
export const ServicesContainer = styled.div`
height: 800px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #010606;
@media screen and (max-width: 768px) {
height: 1100px;
}
@media screen and (max-width: 480px) {
height: 1300px;
}
`
export const ServicesWrapper = styled.div`
max-width: 1000px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
grid-gap: 16px;
padding: 0 50px;
@media screen and (max-width: 1000px){
grid-template-columns: 1fr 1fr;
}
@media screen and (max-width: 768px) {
grid-template-columns: 1fr;
padding: 0 20px;
}
`
export const ServicesCard = styled.div`
background: #fff;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
border-radius: 10px;
max-height: 340px;
padding: 30px;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
transition: all 0.2 ease-in-out;
&:hover{
transform: scale(1.02);
transition : all 0.2s ease-in-out;
cursor: pointer;
}
`
export const ServicesIcon = styled.img`
height:160px;
width: 160px;
margin-bottom: 10px;
`
export const ServicesH1 = styled.h1`
font-size: 2.5rem;
color: #fff;
margin-bottom: 64px;
@media screen and (max-width: 480px) {
font-size: 2rem;
}
`
export const ServicesH2 = styled.h2`
font-size: 1rem;
margin-bottom: 10px;
`
export const ServicesP = styled.p`
font-size: 1rem;
text-align: center;
`
다음 index
에서 해당 엘리먼트들을 가져온다. Icon 1,2,3의 경우는 svg이미지를 가져와서 images
폴더 내에 집어넣고 사용한다.
import { ServicesCard, ServicesContainer, ServicesH1, ServicesH2, ServicesIcon, ServicesP, ServicesWrapper } from './ServiceElements'
import Icon1 from '../../images/svg-4.svg';
import Icon2 from '../../images/svg-5.svg';
import Icon3 from '../../images/svg-6.svg';
현재 상황
세개의 카드 엘리먼트가 제대로 정렬된 모습이다.
Creating Sign in Page
회원가입을 위한 페이지를 작성한다.Pages
에 서 signin.js
를 생성한다.
import React from 'react'
const SigninPage = () => {
return (
<div>
<h1>Sign in Page</h1>
</div>
)
}
export default SigninPage
Adding React Routes to Website
Routes
를 활용해서 path에 따라 다른 페이지를 렌더링하게 된다.
import './App.css';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom'
import Home from './pages';
import SigninPage from './pages/signin';
function App() {
return (
<Router>
<Routes>
<Route path="/" exact={true} element={<Home/>}/>
<Route path="/signin" exact={true} element={<SigninPage/>}/>
</Routes>
</Router>
);
}
export default App;
현재 상황
http://localhost:3000/signin
Creating Footer
사이트의 하단에 footer를 제작한다.
components
폴더에 Footer
폴더를 생성하고 그 내부에 index
와 FooterElements
를 생성한다.
index.js
import React from 'react'
import { FooterContainer, FooterLink, FooterLinkItems, FooterLinksContainer, FooterLinksWrapper, FooterLinkTitle, FooterWrap } from './FooterElements'
const Footer = () => {
return (
<FooterContainer>
<FooterWrap>
<FooterLinksContainer>
<FooterLinksWrapper>
<FooterLinkItems>
<FooterLinkTitle>About us</FooterLinkTitle>
<FooterLink to="/signin">How it works</FooterLink>
<FooterLink to="/signin">Testimonials</FooterLink>
<FooterLink to="/signin">careers</FooterLink>
<FooterLink to="/signin">Investors</FooterLink>
<FooterLink to="/signin">Terms of Service</FooterLink>
</FooterLinkItems>
<FooterLinkItems>
<FooterLinkTitle>Contact Us</FooterLinkTitle>
<FooterLink to="/signin">Contact</FooterLink>
<FooterLink to="/signin">Support</FooterLink>
<FooterLink to="/signin">Destinations</FooterLink>
<FooterLink to="/signin">Sponsorships</FooterLink>
</FooterLinkItems>
</FooterLinksWrapper>
<FooterLinksWrapper>
<FooterLinkItems>
<FooterLinkTitle>Viedeos</FooterLinkTitle>
<FooterLink to="/signin">Submit Video</FooterLink>
<FooterLink to="/signin">Ambassadors</FooterLink>
<FooterLink to="/signin">Agency</FooterLink>
<FooterLink to="/signin">Influencer</FooterLink>
</FooterLinkItems>
<FooterLinkItems>
<FooterLinkTitle>Social Media</FooterLinkTitle>
<FooterLink to="/signin">Instagram</FooterLink>
<FooterLink to="/signin">Facebook</FooterLink>
<FooterLink to="/signin">Youtube</FooterLink>
<FooterLink to="/signin">Twitter</FooterLink>
</FooterLinkItems>
</FooterLinksWrapper>
</FooterLinksContainer>
</FooterWrap>
</FooterContainer>
)
}
export default Footer
FooterLinksContainer
를 2개로 작성하고 해당 container
내부에 2개의 FooterLinkItems
를 추가하는 방식으로 구현했다.
Styling Footer
import styled from 'styled-components';
import {Link} from 'react-router-dom';
export const FooterContainer =styled.footer`
background-color: #101522;
`
export const FooterWrap = styled.div`
padding: 48px 24px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 1100px;
margin: 0 auto;
`
export const FooterLinksContainer = styled.div`
display: flex;
justify-content: center;
@media screen and (max-width: 820px){
padding-top: 32px;
}
`
export const FooterLinksWrapper = styled.div`
display: flex;
@media screen and (max-width: 820px) {
flex-direction: column;
}
`
export const FooterLinkItems = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 16px;
text-align: left;
width: 160px;
box-sizing: border-box;
color: #fff;
@media screen and (max-width: 420px) {
margin: 0;
padding: 10px;
width: 100%;
}
`
export const FooterLinkTitle = styled.h1`
font-size: 14px;
margin-bottom: 16px;
`
export const FooterLink = styled(Link)`
color: #fff;
text-decoration: none;
margin-bottom: 0.5rem;
font-size: 14px;
&:hover {
color: #01bf71;
transition: 0.3s ease-out;
}
`
import styled from 'styled-components';
import {Link} from 'react-router-dom';
export const FooterContainer =styled.footer`
background-color: #101522;
`
export const FooterWrap = styled.div`
padding: 48px 24px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 1100px;
margin: 0 auto;
`
export const FooterLinksContainer = styled.div`
display: flex;
justify-content: center;
@media screen and (max-width: 820px){
padding-top: 32px;
}
`
export const FooterLinksWrapper = styled.div`
display: flex;
@media screen and (max-width: 820px) {
flex-direction: column;
}
`
export const FooterLinkItems = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 16px;
text-align: left;
width: 160px;
box-sizing: border-box;
color: #fff;
@media screen and (max-width: 420px) {
margin: 0;
padding: 10px;
width: 100%;
}
`
export const FooterLinkTitle = styled.h1`
font-size: 14px;
margin-bottom: 16px;
`
export const FooterLink = styled(Link)`
color: #fff;
text-decoration: none;
margin-bottom: 0.5rem;
font-size: 14px;
&:hover {
color: #01bf71;
transition: 0.3s ease-out;
}
`
현재 상황
Footer 영역에 SocialMedia 엘리먼트를 추가 한다.클릭하면 youtube, twitter, instagram 등에 접속할수 있게 하는 방식으로 작성한다.
작성위치는 FooterLinksContainer
가 끝나고 바로 다음부터이다.
<SocialMedia>
<SicialMediaWrap>
<SocialLogo tp='/'>
dolla
</SocialLogo>
<WebsiteRgights>dolla ⓒ {new Date().getFullYear()} All rights reserved. </WebsiteRgights>
<SocialIcons>
<SocialIconLink href="/" target="_blank"
arai-label="Facebook">
<FaFacebook/>
</SocialIconLink>
<SocialIconLink href="/" target="_blank"
arai-label="Instagram">
<FaInstagram/>
</SocialIconLink>
<SocialIconLink href="/" target="_blank"
arai-label="Youtube">
<FaYoutube/>
</SocialIconLink>
<SocialIconLink href="/" target="_blank"
arai-label="Twitter">
<FaTwitter/>
</SocialIconLink>
<SocialIconLink href="/" target="_blank"
arai-label="Linkedin">
<FaLinkedin/>
</SocialIconLink>
</SocialIcons>
</SicialMediaWrap>
</SocialMedia>
FooterElements
export const SocialMedia = styled.section `
max-width: 1000px;
width: 100%;
`
export const SocialMediaWrap = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1100px;
margin: 40px auto 0 auto;
@media screen and (max-width: 820px) {
flex-direction: column;
}
`
export const SocialLogo = styled(Link)`
color: #fff;
justify-self: start;
cursor: pointer;
text-decoration: none;
font-size: 1.5rem;
display: flex;
align-items: center;
margin-bottom: 16px;
font-weight: bold;
`
export const WebsiteRights = styled.small`
color: #fff;
margin-bottom: 16px;
`
export const SocialIcons = styled.div`
display: flex;
justify-content: space-between;
align-items: conter;
width: 240px;
`
export const SocialIconLink =styled.a`
color:#fff;
font-size: 24px;
`
현재상황
Footer 가장 아래쪽에 로고와 저작권, 그리고 소셜미디어 아이콘이 표시되어있다.
Changing Navbar Background on Scroll
사용자가 일정 구간 이상을 스크롤로 내리면 Navbar의 배경색이 변경된다.
먼저 일정 구간이상 이동하면 Navbar
의 배경이 검은색으로 나타나는 느낌을 나타내보자.
NavbarElements
에서 Nav의 background를 변경한다.
background: ${({scrollNav}) => (scrollNav? "#000" : "transparent" )};
scrollNav라는 값이 true이면 검은색으로, false일 경우에는 투명하게 변경된다.
index에서 이를 usestate로 scrollNav값을 관리한다.
const [scrollNav, setScrollNav] = useState(false)
const changeNav = () => {
if(window.scrollY >= 80) {
setScrollNav(true)
} else {
setScrollNav(false)
}
}
useEffect(()=> {
window.addEventListener('scroll',changeNav)
},[])
만약 사용자가 80px이상 스크롤을 하면 scrollNav가 true로 변경된다. ( 검은색으로 배경이 바뀐다.)
그리고 scrollnav 값을 Nav에서 전달해야 하므로 return 값에서 Nav의 props
에 scrollNav
를 전달한다.
그렇게 해주고 실행하면
첫화면이 하얀색으로 되어 텍스트를 보기가 힘들어졌다.
여러가지 방법이 있지만 이번에는 아래의 영상 배경화면쪽으로 margin-top
을 적용해서 이동시켰다.
components/Navbar/NavberElements.js
export const Nav = styled.nav`
background: ${({scrollNav}) => (scrollNav? "#000" : "transparent" )};
height: 80px;
margin-top:-80px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
position: sticky;
top: 0;
z-index: 10;
@media screen and(max-width: 960px) {
transition: 0.8s all ease;
}
`;
margin-top
을 -80px만큼 적용시켜서 아래쪽으로 80px만큼 내려오게 했다.
현재 상황
Adding React Scroll to Our Site
- 로고를 클릭하면 맨 위로 이동한다.
-NavBar
항목을 클릭하면 그에 해당하는 내용이 있는 쪽으로 스크롤된다.
-
NavBar
항목을 클릭하면 그에 해당하는 내용이 있는 쪽으로 스크롤된다. 이 2가지를 구현한다.
Navbar/index
에서 toggleHome
을 작성한다.
const toggleHome = () => {
scroll.scrollToTop()
}
여기서 scroll
은 react-scroll
에서 animateScroll
을 scroll
로 정의하고 import 한 것이다.
import {animateScroll as scroll} from 'react-scroll'
NavLogo
에 onClick
으로 toggleHome
을 적용시키고 실행하면
로고를 클릭하면 위로 올라가게 된다.
NavBar 항목을 클릭하면 해당 내용으로 스크롤 이동하는것은 각각의 NavLinks에 react-scroll
속성을 부여한다.
<NavLinks to="about"
smooth={true}
duration={500}
spy={true}
exact='true'
offset={-80}
>About</NavLinks>
smooth = 스크롤 애니메이션 적용여부
duration = 애니메이션 시간
spy = 스크롤이 해당 위치에 도달하면 active 상태가 된다.
exact = path 속성에 넣은 경로값이 정확히 URL의 경로값과 일치할 때만 렌더링되도록 돕는다.
offset = 시작지점 조절, 작성자의 경우는 첫번째의 NavBar 배경 때문에 margin-bottom
값을 -80px 만큼 설정했으므로 기본설정으로 애니메이션이 동작하면 80px 위에서 멈추게 된다.
위와 같이 다른 NavBar 항목들도 설정해주면 된다.
현재 상황
Creating Sign in Componenet
Sign in 버튼을 눌렀을때 나타나는 페이지를 생성한다.
components/Signin/index
import React from 'react'
import { Container, Form, FormButton, FormContent, FormH1, FormInput, FormLabel, FromWrap, Icon, Text } from './SigninElements'
const SignIn = () => {
return (
<>
<Container>
<FromWrap>
<Icon to="/"> dolla</Icon>
<FormContent>
<Form action="#">
<FormH1>Sign in to your account</FormH1>
<FormLabel htmlFor='for'>Email</FormLabel>
<FormInput type='email'/>
<FormLabel htmlFor='for'>Password</FormLabel>
<FormInput type='password' required/>
<FormButton type='submit'>Continue</FormButton>
<Text>Forgot passowrd</Text>
</Form>
</FormContent>
</FromWrap>
</Container>
</>
)
}
export default SignIn
components/Signin/SgininElements
import styled from 'styled-components';
import {Link} from 'react-router-dom';
export const Container = styled.div`
min-height:692px;
position: fixed;
bottom:0;
left: 0;
right: 0;
top: 0;
z-index: 0;
overflow:hidden;
background: linear-gradient(
108deg,
rgba(1, 147, 86, 1) 0%,
rgba(10, 201, 122, 1) 100%
);
`
export const FromWrap = styled.div`
height: 100%auto;
display: flex;
flex-direction: column;
justify-content: center;
@media screen and (max-width: 400px) {
height: 80%auto;
}
`;
export const Icon = styled(Link)`
margin-left: 32px;
margin-top: 32px;
text-decoration: none;
color: #fff;
font-weight: 700;
font-size: 32px;
@media screen and (max-width:480px) {
margin-left: 16px;
margin-top: 8px;
}
`
export const FormContent = styled.div`
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
@media screen and (max-width: 480px) {
padding: 10px;
}
`;
export const Form = styled.form`
background: #010101;
max-width: 400px;
height: auto;
width: 100%;
z-index: 1;
display: grid;
margin: 0 auto;
padding: 80px 32px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
@media screen and (max-width: 480px){
padding: 32px 32px;
}
`
export const FormH1 = styled.h1`
margin-bottom: 40px;
color: #fff;
font-size: 20px;
font-weight: 400;
text-align: center;
`
export const FormLabel = styled.label`
margin-bottom: 8px;
font-size: 14px;
color: #fff;
`
export const FormInput = styled.input`
padding: 16px 16px;
margin-bottom: 32px;
border: none;
border-radius: 4px;
`
export const FormButton =styled.button`
background: #01bf71;
padding: 16px 0;
border: none;
border-radius: 4px;
color: #fff;
font-size: 20px;
cursor: pointer;
`;
export const Text = styled.span`
text-align:center;
margin-top: 24px;
color: #fff;
font-size: 14px;
`
현재 상황
Scroll to Top onPage/RouteChange
route가 제대로 이루어졌는지 확인하기 위한 요소를 만든다.
Signin/ScrollToTop.js
import {useEffect} from 'react'
import {useLocation} from 'react-router-dom'
export default function ScrollToTop(){
const {pathname} = useLocation()
useEffect(() => {
window.scrollTo(0,0)
},
[pathname]
);
return null;
}
`pages/signin.js
import React from 'react'
import SignIn from '../components/Signin'
import ScrollToTop from '../components/Signin/ScrollToTop'
const SigninPage = () => {
return (
<div>
<ScrollToTop/>
<SignIn/>
</div>
)
}
export default SigninPage
HeroSection
의 버튼을 눌렀을 때 homeObjThree
로 스크롤되도록 HeroSection/index
에서 Button 컴포넌트에 속성을 추가한다. 이는 이전에 스크롤이동 애니메이션 작업을 할때 작성한 코드와 동일하다.
<Button
to='signup'
onMouseEnter={onHover}
onMouseLeave={onHover}
primary='true'
dark='true'
smooth={true}
duration={500}
spy={true}
exact='true'
offset={-80}
>
Get started {hover ? <ArrowForward/> : <ArrowRight/>}
</Button>
그리고 HeroContainer
에 id값을 home
으로 지정해서 InfoSection 에서 만든 엘리먼트들의 버튼을 클릭했을때 전부 HeroContainer 로 스크롤 되게 한다.
최종 상태
이로써 Nav, SideMenu, HeroSection, InfoSection, ServiceSection, Footer, 컴포넌트를 제작하고, 항목을 클릭했을때 원하는 위치로 스크롤이동, sing in 페이지 제작까지 완료되었다.
Author And Source
이 문제에 관하여(리액트 프로젝트 만들기 - 2), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jhs000123/리액트-프로젝트-만들기-2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)