영화 나열을 위한 Row 컴포넌트 생성하기
전체 구조
import React from 'react'
export default function Row() {
return (
<div>Row</div>
)
}
다음과 같이 Row Props를 내려준다.
<App.js>
import requests from './api/requests';
import './App.css';
import Nav from './components/Nav';
import Banner from './components/Banner';
import Row from './components/Row';
function App() {
return (
<div className="App">
<Nav />
<Banner />
<Row
title="NETFLIX ORIGINALS"
id="NO" //ETFLIX ORIGINALS 줄임말
fetchUrl={requests.fetchNetflixOrignials}
isLargeRow
/>
<Row title="Trending Now" id="TN" fetchUrl={requests.fetchTrending}/> //이렇게
<Row title="Top Rated" id="TR" fetchUrl={requests.fetchTopRated}/> //이렇게
<Row title="Action Movies" id="AM" fetchUrl={requests.fetchActionMovies}/> //이렇게
<Row title="Comedy Movies" id="CM" fetchUrl={requests.fetchComedyMovies}/> //이렇게
</div>
);
}
export default App;
<Row.js>
import React from 'react'
export default function Row({isLargeLow, title, id, fetchUrl}) {
return (
<div>Row</div>
)
}
<Row.js>
import React, { useEffect, useState } from 'react'
import axios from '../api/axios';
export default function Row({isLargeRow, title, id, fetchUrl}) {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetchMovieData();
}, []);
const fetchMovieData = async () => {
const request = await axios.get(fetchUrl);
setMovies(request.data.results)
};
return (
<div>Row</div>
)
}
<Row.js>
import React, { useEffect, useState } from 'react'
import axios from '../api/axios';
import "./Row.css"
export default function Row({isLargeRow, title, id, fetchUrl}) {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetchMovieData();
}, []);
const fetchMovieData = async () => {
const request = await axios.get(fetchUrl);
setMovies(request.data.results)
};
return (
<section className='row'>
<h2>{title}</h2>
<div className='slider'>
<div className='slider__arrow-left'>
<span className="arrow">{"<"}</span>
</div>
<div id={id} className="row__posters">
{movies.map(movie => (
<img
key={movie.id}
className={`row__poster ${isLargeRow && "new__posterLarge"}`}
src={`https://image.tmdb.org/t/p/original/${
isLargeRow ? movie.poster_path : movie.backdrop_path
} `}
alt={movie.name}
/>
))}
</div>
<div className='slider__arrow-right'>
<span className='arrow'>{">"}</span>
</div>
</div>
</section>
)
}
<Row.css>
.row {
margin-left: 20px;
color: white;
}
h2 {
padding-left: 20px;
}
.slider {
position: relative;
}
.slider__arrow-left {
background-clip: content-box;
padding: 20px 0;
box-sizing: border-box;
transition: 400ms all ease-in-out;
cursor: pointer;
width: 80px;
z-index: 1000;
position: absolute;
left: 0;
top: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
.slider__arrow-right {
padding: 20px 0;
background-clip: content-box;
box-sizing: border-box;
transition: 400ms all ease-in-out;
cursor: pointer;
width: 80px;
z-index: 1000;
position: absolute;
right: 0;
top: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
.arrow {
transition: 400ms all ease-in-out;
}
.arrow:hover {
transition: 400ms all ease-in-out;
transform: scale(1.5);
}
.slider:hover .slider__arrow-left {
transition: 400ms all ease-in-out;
visibility: visible;
}
.slider:hover .slider__arrow-right {
transition: 400ms all ease-in-out;
visibility: visible;
}
.slider__arrow-left:hover {
background: rgba(20, 20, 20, 0.5);
transition: 400ms all ease-in-out;
}
.slider__arrow-right:hover {
background: rgba(20, 20, 20, 0.5);
transition: 400ms all ease-in-out;
}
.row__posters {
display: flex;
overflow-y: hidden;
overflow-x: scroll;
padding: 20px 0 20px 20px;
scroll-behavior: smooth;
}
.row__posters::-webkit-scrollbar {
display: none;
}
.row__poster {
object-fit: contain;
width: 100%;
max-height: 144px;
margin-right: 10px;
transition: transform 450ms;
border-radius: 4px;
}
.row__poster:hover {
transform: scale(1.08);
}
.row__posterLarge {
max-height: 320px;
}
.row__posterLarge:hover {
transform: scale(1.1);
opacity: 1;
}
.row__arrow-left {
position: absolute;
top: 0;
left: 20px;
height: 100%;
width: 32px;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
}
.row__arrow-right {
position: absolute;
top: 0;
right: 0px;
height: 100%;
width: 32px;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
}
@media screen and (min-width: 1200px) {
.row__poster {
max-height: 160px;
}
.row__posterLarge {
max-height: 360px;
}
}
@media screen and (max-width: 768px) {
.row__poster {
max-height: 100px;
}
.row__posterLarge {
max-height: 280px;
}
}
.swiper-pagination {
text-align: right !important;
}
.swiper-pagination-bullet {
background: gray !important;
opacity: 1 !important;
}
.swiper-pagination-bullet-active {
background: white !important;
}
.swiper-button-prev {
color: white !important;
}
.swiper-button-next {
color: white !important;
}
.swiper-button-next:after, .swiper-button-prev:after{
font-size: 1.3rem !important;
font-weight: 600 !important;
}
그리고 배경을 검정색으로 채우기 위해,
<App.css>
.App {
background-color: #000;
}
이를 모두 적용시켜주면, 다음과 같은 결과를 볼 수 있다.
슬라이드 기능 추가하기
getElementById는 id로 Element를 찾는것이다.
Element.scrollLeft 속성은 요소의 콘텐츠가 왼쪽 가장자리에서 스크롤되는 픽셀 수를 가져오거나 설정한다.
이 scrollLeft를 통해 화살표 방향 버튼을 클릭하면 일정 픽셀만큼 슬라이드가 되는 것을 구현할 것이다.
자세한 설명은 아래 링크에 있다.
링크텍스트
우리는 scrollLeft를 통해 화살표 방향 버튼을 클릭하면 일정 픽셀만큼 슬라이드가 되는 것을 구현할 것이다.
우리는 전체 길이만큼 슬라이드를 해줄 것이기 때문에 '일정 픽셀 = 전체 길이' 이다.
이 때, innerWidth를 사용하면 된다.
document.getElementById(id).scrollLeft = document.getElementById(id).scrollLeft - (window.innerWidth - 80)
이런식으로 말이다.
코드는 다음과 같다.
<Row.js>
import React, { useEffect, useState } from 'react'
import axios from '../api/axios';
import "./Row.css"
export default function Row({isLargeRow, title, id, fetchUrl}) {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetchMovieData();
}, []);
const fetchMovieData = async () => {
const request = await axios.get(fetchUrl);
setMovies(request.data.results)
};
return (
<section className='row'>
<h2>{title}</h2>
<div className='slider'>
<div className='slider__arrow-left'>
<span className="arrow"
onClick={() => {
document.getElementById(id).scrollLeft -= window.innerWidth - 80;
}}> // 이 onclick 부분을 적용시키면 슬라이드가 구현됨.
{"<"}
</span>
</div>
<div id={id} className="row__posters">
{movies.map(movie => (
<img
key={movie.id}
className={`row__poster ${isLargeRow && "new__posterLarge"}`}
src={`https://image.tmdb.org/t/p/original/${
isLargeRow ? movie.poster_path : movie.backdrop_path
} `}
alt={movie.name}
/>
))}
</div>
<div className='slider__arrow-right'>
<span className='arrow'
onClick={() => {
document.getElementById(id).scrollLeft += window.innerWidth - 80;
}}> // 이 onclick 부분을 적용시키면 슬라이드가 구현됨.
{">"}
</span>
</div>
</div>
</section>
)
}
코드를 적용시키면 다음과 같이 슬라이드가 되는 것을 볼 수 있다
Styled Component 를 이용해서 Footer 생성하기
<Footer.js>
import React from 'react'
import { styled } from 'styled-components'
export default function Footer() {
return (
<FooterContainer>
<FooterContent>
<FooterLinkContainer>
<FooterLinkTitle>
넷플릭스 대한민국
</FooterLinkTitle>
<FooterLinkContent>
<FooterLink href="https://help.netflix.com/ko/node/412">
넷플릭스 소개
</FooterLink>
<FooterLink href="https://help.netflix.com/ko">
고객 센터
</FooterLink>
<FooterLink href="https://help.netflix.com/ko">
미디어 센터
</FooterLink>
<FooterLink href="https://help.netflix.com/ko">
이용 약관
</FooterLink>
</FooterLinkContent>
<FooterDescContainer>
<FooterDescRights>
Netflix Rights Reserved.
</FooterDescRights>
</FooterDescContainer>
</FooterLinkContainer>
</FooterContent>
</FooterContainer>
)
}
const FooterContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0;
border: -top 1px solid rgb(25, 25, 25);
width: 100%;
position: relative;
z-index: 100;
@media (max-width: 769px) {
padding:20px 20px;
padding-bottom: 30px;
}
`;
const FooterContent = styled.div``;
const FooterLinkContainer = styled.div`
width: 500px;
@media (max-width: 768px) {
width: 100%;
}
`;
const FooterLinkTitle = styled.h1`
color: gray;
font-size: 17px;
`;
const FooterLinkContent = styled.div`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-top: 35px;
@media (max-width: 769px) {
margin-top: 26px;
}
`;
const FooterLink = styled.a`
color: gray;
font-size: 14px;
width: 110px;
margin-bottom: 21px;
text-decoration: none;
&:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
margin-bottom: 16px;
}
`;
const FooterDescContainer = styled.div`
margin-top: 30px;
@media (max-width : 768px) {
margin-top: 20px;
}
`;
const FooterDescRights = styled.h2`
color: white;
font-size: 14px;
text-align: center;
`;
그리고 App.js에서 import해주고 띄워준다.
<App.js>
import requests from './api/requests';
import './App.css';
import Nav from './components/Nav';
import Banner from './components/Banner';
import Row from './components/Row';
import Footer from './components/Footer'; //이렇게 import하고,
function App() {
return (
<div className="App">
<Nav />
<Banner />
<Row
title="NETFLIX ORIGINALS"
id="NO" //ETFLIX ORIGINALS 줄임말
fetchUrl={requests.fetchNetflixOrignials}
isLargeRow
/>
<Row title="Trending Now" id="TN" fetchUrl={requests.fetchTrending}/>
<Row title="Top Rated" id="TR" fetchUrl={requests.fetchTopRated}/>
<Row title="Action Movies" id="AM" fetchUrl={requests.fetchActionMovies}/>
<Row title="Comedy Movies" id="CM" fetchUrl={requests.fetchComedyMovies}/>
<Footer/> // 이렇게 띄워준다.
</div>
);
}
export default App;
영화 자세히 보기 클릭 시 모달 생성하기
<Row.js>
import React, { useEffect, useState } from 'react'
import axios from '../api/axios';
import MovieModal from "./MovieModal";
import "./Row.css";
export default function Row({isLargeRow, title, id, fetchUrl}) {
const [movies, setMovies] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [movieSelected, setMovieSelected] = useState({});
useEffect(() => {
fetchMovieData();
}, []);
const fetchMovieData = async () => {
const request = await axios.get(fetchUrl);
setMovies(request.data.results)
};
const handleClick = (movie) => {
setModalOpen(true) //함수가 call됐을 때 true로 만들어주고,
setMovieSelected(movie); // 여기에 정보가 들어있다.
}; // movie 정보를 가져오고
return (
<section className='row'>
<h2>{title}</h2>
<div className='slider'>
<div className='slider__arrow-left'>
<span className="arrow"
onClick={() => {
document.getElementById(id).scrollLeft -= window.innerWidth - 80;
}}>
{"<"}
</span>
</div>
<div id={id} className="row__posters">
{movies.map(movie => (
<img
key={movie.id}
className={`row__poster ${isLargeRow && "new__posterLarge"}`}
src={`https://image.tmdb.org/t/p/original/${
isLargeRow ? movie.poster_path : movie.backdrop_path
} `}
alt={movie.name}
onClick={() => handleClick(movie)} // handleClick 안에 movie라는 정보를 넣어줌.
/>
))}
</div>
<div className='slider__arrow-right'>
<span className='arrow'
onClick={() => {
document.getElementById(id).scrollLeft += window.innerWidth - 80;
}}>
{">"}
</span>
</div>
</div>
{
//modalOpen이 true일 때는, MovieModal 컴포넌트를 보여준다.
modalOpen && (
<MovieModal {...movieSelected} setModalOpen={setModalOpen}/>
)
}
</section>
)
}
<index.js>
import React from "react";
import "./MovieModal.css";
function MovieModal({
backdrop_path,
title,
overview,
name,
release_date,
first_air_date,
vote_average,
setModalOpen,
}) {
return<div></div>
}
export default MovieModal;
Movie 모달 UI 생성하기
<index.js>
import React from "react";
import "./MovieModal.css";
// MovieModal 함수형 컴포넌트 정의
function MovieModal({
backdrop_path,
title,
overview,
name,
release_date,
first_air_date,
vote_average,
setModalOpen,
})
{
return (
<div className='presentation'> // presentation 클래스를 가진 div
<div className="wrapper-modal"> // wrapper-modal 클래스를 가진 div
<div className="modal"> // modal 클래스를 가진 div
<span onClick={()=> setModalOpen(false)} className='modal-close'> // 닫기 버튼
X
</span>
<img // 포스터 이미지
className='modal__poster-img'
src={`https://image.tmdb.org/t/p/original/${backdrop_path}`}
alt='modal__poster-img'
/>
<div className="modal__content"> // modal__content 클래스를 가진 div
<p className="modal__details"> // modal__details 클래스를 가진 p
<span className="modal__user-perc"> // modal__user-perc 클래스를 가진 span
100% for you
</span>
{release_date ? release_date : first_air_date} // 날짜 표시
</p>
<h2 className="modal__title">{title? title: name}</h2> // 영화 제목 표시
<p className="modal__overview"> 평점: {vote_average}</p> // 평점 표시
<p className="modal_overview"></p> // modal_overview 클래스를 가진 p
</div>
</div>
</div>
</div>
)
}
export default MovieModal; // MovieModal 컴포넌트를 내보냄
<MovieModal.css>
.modal {
position: relative;
/* 현재 위치를 기준으로 위치 지정 */
max-width: 800px;
/* 최대 너비 설정 */
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12);
/* 그림자 효과 */
background: #111;
/* 배경색 */
overflow: hidden;
/* 내용이 너무 많으면 숨김 */
border-radius: 8px;
/* 모서리 둥글게 */
transition: all 400ms ease-in-out 2s;
/* 모든 속성에 대한 전환 효과 */
animation: fadeIn 400ms;
/* 애니메이션 효과 적용 */
}
.modal__poster-img {
width: 100%;
/* 너비 100% */
height: auto;
/* 높이 자동 */
}
.modal__content {
padding: 40px;
/* 내부 여백 설정 */
color: white;
/* 글자색 설정 */
}
.modal__title {
padding: 0;
/* 내부 여백 없음 */
font-size: 40px;
/* 폰트 크기 설정 */
margin: 16px 0;
/* 외부 여백 설정 */
}
.modal__details {
font-weight: 600;
/* 글자 굵기 설정 */
font-size: 18px;
/* 폰트 크기 설정 */
}
.modal__overview {
font-size: 20px;
/* 폰트 크기 설정 */
line-height: 1.5;
/* 줄 간격 설정 */
}
.modal__user-perc {
color: #46d369;
/* 글자색 설정 */
}
.modal::-webkit-scrollbar {
display: none;
/* 스크롤바 숨김 */
visibility: hidden;
/* 스크롤바 숨김 */
}
/* IE, Edge 및 Firefox용 스크롤바 숨기기 */
.modal {
-ms-overflow-style: none;
/* IE 및 Edge */
scrollbar-width: none;
/* Firefox */
}
.wrapper-modal {
position: fixed;
/* 고정 위치 */
inset: 0px;
/* 위치 설정 */
background-color: rgb(0 0 0 / 71%);
/* 배경색 설정 */
-webkit-tap-highlight-color: transparent;
/* 탭 하이라이트 색상 투명 */
display: flex;
/* 유연한 박스 모델 */
justify-content: center;
/* 가운데 정렬 */
}
.presentation {
z-index: 1200;
/* 쌓임 순서 설정 */
position: absolute;
/* 위치 절대 */
}
.modal-close {
position: absolute;
/* 위치 절대 */
right: 20px;
/* 오른쪽에서 20px 거리 */
top: 20px;
/* 위쪽에서 20px 거리 */
cursor: pointer;
z-index: 1000;
color: white;
}
@media screen and (max-height: 768px) {
.wrapper-modal {
align-items: unset;
padding-top: 2rem;
}
.modal {
overflow-y: scroll;
}
}
@media screen and (max-width: 768px) {
.modal__overview {
font-size: 16px;
}
.modal__details {
font-size: 16px;
}
.wrapper-modal {
padding: 0;
}
.modal {
overflow-y: scroll !important;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}