본문 바로가기

공부일지/Project

비전공자의 프로젝트 만들기) disneyplus App 만들기 part2

728x90
반응형

Disneyplus App 만들기 part2

 

오늘은 지난 시간에 만들던 디즈니플러스 클론코딩 배너 부분부터 작업을 진행했다.

 

    const [movie, setMovie] = useState([]);
   
    const truncate = (str, n) => {
        return str?.length > n ? str.substring(0, n-1) + "..." : str
    }
    <header 
        className="banner"
        style={{
            backgroundImage:`url("https://image.tmdb.org/t/p/original/${movie.backdrop_path}")`,
            backgroundPosition: "top center",
            backgroundSize: "cover"
        }}
    >
        <div className="banner__contents">
            <h1 className="banner__title">
                {movie.title||movie.name||movie.original_nam}
            </h1>
            <div className="banner__buttons">
                {movie?.videos?.results[0]?.key && 
                    <button className="banner__button play">
                        play
                    </button>
                }
            </div>
            <p className="banner__description">
                {truncate(movie?.overview, 100)}
            </p>
        </div>
        <div className="banner--fadeBottom"></div>
    </header>

지난 시간에 setMovie에 영화의 정보를 저장까지 진행했는데 배너를 불러오기 위해선 setMovie에 저장되어 있는 영화정보의 썸네일, 비디오의 정보를 가져와서 작업하였다.

header태그에 배경이미지를 지정하고 경로에 적은 https://image.tmdb.org/t/p/original/ 이부분은 themoiedb에서 지정해 둔 방식이라 그대로 사용해야 한다.

 

그리고 미리보기 영상을 가져와야 되는데 미리 보기 영상이 없는 영화도 있을 수 있기 때문에 아래의 코드를 보면 ?. 이렇게 사용했는데 ?의 왼쪽의 정보가 있으면 다음 정보를 가져오라는 뜻이다.

해석을 하자면 영화의 정보에서 비디오가 있고 첫번째 값의 key값이 있으면 비디오 플레이 버튼을 출력하라는 뜻이다.

{movie?.videos?.results[0]?.key && 
    <button className="banner__button play">
        play
    </button>
}

비디오의 정보가 없으면 그대로 배경이미지로 썸네일이 보인다.

 

그리고 description을 가져와서 truncate라는 함수를 만들어서 글자수가 100글자 이사이면 100번째 문자 뒤는 ...으로 변경하라는 함수를 만들었다.

 

그리고 이제 버튼 클릭했을때 영상이 실행되는 기능을 작업했다.

일단 클릭 여부를 확인하기 위해서 isClicked  state를 하나 만들었다.

으흠.. 이럴땐 jquery가 조금 더 편리한 거 같다. state를 안 쓰고 해당 기능을 작업할 순 있겠지만 일단 지금 state로 처리하는 게 최선인 거 같다.

 

  if(isClicked){
    return (
      <>
        <Container>
          <HomeContainer>
            <Iframe
              src={`https://www.youtube.com/embed/${movie.videos.results[0].key}?controls=0&autoplay=1&loop=1&mute=1&playlist=${movie.videos.results[0].key}`}
              width="640"
              height="360"
              frameborder="0"
              allow="autoplay; fullscreen"
            ></Iframe>
          </HomeContainer>
        </Container>
        <button onClick={() => setIsClicked(false)}>X</button>
      </>
    )
  }else{
    return (
      기존 header코드
    );
  }
  
};

const Container = styled.div``;

const HomeContainer = styled.div``;

const Iframe = styled.iframe`
  &:after{}
`;

styled를 사용해 css를 정의해주었고 video를 체크했던 key값을 가져와 사용해 주었다.

그 외 유튜브 파라미터 속성을 넣었다.

그리고 영상을 닫을 때 필요한 isCliked를 false로 변경하면 배너에 영상 출력은 끝이 난다.

이렇게 배너가 나오고 저기 play버튼을 누르면 영상이 실행된다.

 

배너 밑에 출력될 카테고리 영역은 배너 부분보다 조금 코드가 짧으며 카테고리 클릭 시 해당 관련 영상만 나올 수 있는 기능은 넣어주지 않았다.

const Category = () => {
  const cateLists = ["disney", "marvel", "national", "pixar", "starwars"]
  return (
    <Container>
      {cateLists.map((cate, index) => 
        <Wrap key={index}>
          <img src={`/images/viewers-${cate}.png`} alt="disney" />
          <video autoPlay loop muted>
            <source src={`/videos/${cate}.mp4`} type="video/mp4" />
          </video>
        </Wrap>
      )}      
    </Container>
  )
}

export default Category

const Container = styled.div``;

const Wrap = styled.div``;

5가지의 카테고리를 만든 후 map으로 하나씩 뿌려주면서 img와 영상을 정보를 보여주며 영상은 투명도를 주어 0으로 하고 마우스 올렸을 때 나올 수 있도록 하였다.

 

그리고 row부분을 작업하는데 row는 슬라이드 기능이 필요해 swiper을 사용하였다.

 

일단 먼저 Row가 출력될 페이지에서 row의 fetchUrl, title, id를 전달한다.

            <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.fetchComedMovies} />

이렇게 4종류의 row를 만들어서 각각 fetchUrl, title, id를 전달해 준다.

  const [movies, setMovies] = useState([])

  const fetchMovieData = useCallback( async () => {
    const response = await axios.get(fetchUrl)
    setMovies(response.data.results);
  }, [fetchUrl])
  
  useEffect(() => {
    fetchMovieData()
  }, [fetchMovieData])

전달받은 fetchUrl을 사용해 데이터를 전달받아 setMoives에 저장한다.

useCallback을 사용한 이유는 리렌더링 될 때마다 데이터를 다시 전송하여도 받은 데이터를 Movies에 저장할 필요가 없기 때문에 fetchUrl이 변경되지 않으면 함수를 실행되지 않게 조금이라도 렌더링 속도를 줄이기 위해 사용하였다.

그리고 useEffect를 사용해서 fetchMovieData가 변화가 있을 때 렌더링 되게 하였다.

 

<Container>
      <h2>{title}</h2>
      <Swiper
      modules={[Navigation, Pagination, Scrollbar, A11y, Autoplay]}
      loop={true}
      navigation
      pagination={{ clickable: true }}
      autoplay={{ delay: 1000 }}
      breakpoints={{
        1378: {
          slidesPerView: 6,
          slidesPerGroup: 6,
        },
        998: {
          slidesPerView: 5,
          slidesPerGroup: 5,
        },
        625: {
          slidesPerView: 4,
          slidesPerGroup: 4,
        },
        0: {
          slidesPerView: 3,
          slidesPerGroup: 3,
        },
      }}
      >
        <Content id={id}>
          {movies.map((movie) => 
            (
              <SwiperSlide key={movie.id}>
                <Wrap>
                  <img 
                    className="row__poster"
                    src={`https://image.tmdb.org/t/p/original${movie.backdrop_path}`}
                    alt={movie.name}
                  />
                </Wrap>
              </SwiperSlide>
            )
          )}
        </Content>
      </Swiper>
    </Container>

Container영역을 보면

 

swiper을 사용해서 한 화면에 특정 개수만큼 보여주고 페이징, 화살표, 슬라이드, 자동플레이 기능을 추가하였다.

자세한 swiper 기능은 공식홈페이지에 자세히 나와있다.

 

영화의 정보를 배열로 저장하였기 때문에 map을 사용해 하나씩 출력하였다.

 

이제 영화 썸네일을 클릭하면 나올 팝업창을 작업했다.

 

  const [modalOpen, setModalOpen] = useState(false)
  const [movieSelected, setMovieSelected] = useState({})
  
  const handleClick = (movie) => {
    setModalOpen(true);
    setMovieSelected(movie);
  }
  
  <Container>
      <h2>{title}</h2>
      <Swiper>
            <SwiperSlide key={movie.id}>
                <Wrap>
                  <img 
                    className="row__poster"
                    src={`https://image.tmdb.org/t/p/original${movie.backdrop_path}`}
                    alt={movie.name}
                    onClick={()=> handleClick(movie)}
                  />
                </Wrap>
              </SwiperSlide>
      </Swiper>

      {modalOpen &&
        <MovieModal
        {...movieSelected}
        setModalOpen={setModalOpen} 
        />
      }
    </Container>

 

영화를 클릭했는지 확인하는 state와 클릭했을 때 해당 영화의 정보를 저장하기 위한 state 두 가지를 만들고

 

modalOpen 상태가 true일 때 팝업창이 나오는 컴포넌트를 하나 만들어야 하기 때문에 컴포넌트 폴더 안에 MovieModal 폴더를 하나 생성해 준다.

const MovieModal = ({ backdrop_path, title, overview, name, release_date, first_air_date, vote_average, setModalOpen }) => {

    const ref = useRef();
    useOnCLickOutside(ref, ()=>{
        setModalOpen(false)
    })

    return (
        <div className="presentation" role="presentation">
            <div className='wrapper-modal'>
                <div className='modal' ref={ref}>
                    <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-img"
                    />
                    <div className='modal__content'>
                        <p className='modal__details'>
                            <span className='modal__user_perc'>100% for you</span>{" "}
                            {release_date ? release_date : first_air_date}
                        </p>
                        <h2 className='nodal__title'>{title ? title: name}</h2>
                        <p className='modal__overview'>평점 {vote_average}</p>
                        <p className='modal__overview'>평점 {overview}</p>
                    </div>
                </div>
            </div>
        </div>
    )
}

props로 전달받은 props의 값을 바로 사용하기 위해 중괄호(디스트럭팅)로 감싸고 필요한 데이터들을 각각 선언하였다.

 

여기선 useRef를 사용하였는데 useRef는 제이쿼리나 자바스크립트를 사용할 때 해당 엘리먼트를 선택할 때 사용했던 방식이 있었는데 react에서는 useRef를 사용하기 때문에 ref를 사용하면 된다.

 

팝업창은 그냥 전달받은 props를 디자인에 맞게 하나씩 적어서 사용하면 끝이다.

소스코드를 보면 크게 어려운 게 없을 것이다.

 

다음시간에는 검색부분과 시간이 된다면 firebase 연결 작업을 진핼할것이다.

파이팅

728x90
반응형