내일배움캠프 React_7기 TIL - 10.[JavaScript 개인과제] 영화 검색 사이트 만들기 (1)

·2024년 10월 16일
1
post-thumbnail

Vanilla JS 와 TMDB에서 제공하는 영화 API를 사용하여 영화 검색 사이트를 만들고자 한다.

어제부터 작업을 시작하려고 했는데, TMDB 회원가입 이슈 (인증메일외않줌 이슈;)로 인해 오늘부터 작업을 시작하게 되었다

우선 Git Repository 생성하고 GitHub Pages로 호스팅 했다.

방문은 여기 -> 영화 검색 사이트

TMDB API key 발급

회원가입이 제일 어려웠다.

프로필 -> 설정으로 들어가서 API 키 발급을 요청하면 별도의 대기 없이 바로 키와 토큰이 발급된다.

key가 발급되었으면, 이제 API값을 받아올 수 있도록 fetch 해주면 된다.

TMDB API Reference 를 참고했다.

HEADER에 발급받은 key또는 토큰을 넣으면 Fetch Request를 알아서 말아준다.
(나는 key로는 401이 떠서 토큰으로 했더니 200 떴음)

const options = {
  method: "GET",
  headers: {
    accept: "application/json",
    Authorization:
      "Bearer Your API KEY",
  },
};

let movies; //영화 리스트 담을 변수
let totalPages; //전체 페이지 데이터
let total_result = document.getElementById("total-result"); //전체 결과 건수

//영화 데이터 받아오기 (fetch)
const fetchMovies = async (page) => {
  console.log("fetchMovies 실행");
  try {
    const response = await fetch(
      `https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=ko-KR&page=1&sort_by=popularity.desc`,
      options
    );
    const data = await response.json();
    console.log(data); // 응답 데이터를 출력 콘솔
    if (data.results) {
      movies = data.results; // 받아온 데이터를 movies에 저장
      totalPages = data.total_pages;
      total_result.innerText = "검색 건수 : " + data.total_results + "건";
      return movies; // movies 반환
    } else {
      console.error("영화 데이터를 가져오는 데 실패했습니다.", data);
    }
  } catch (err) {
    console.error(err);
  }
};

async , await 사용

async await 를 사용하여 API를 받아왔다.

async : 함수 앞에 붙여 비동기 함수임을 선언한다.(반환값 : Promise)
await : Promise가 처리될 때 까지 기다리다가 처리가 완료되면 다음 작업 실행.

json으로 받아온 응답값을 data에 넣고, data로 무슨 값이 오는지 콘솔로 확인해봤다.

이런 데이터를 가져와서 사용할 수 있겠구나 ~ 했다.
이렇게 데이터가 성공적으로 받아지면 movies에 저장하고, 실패 시 에러 메시지를 출력하도록 했다.

영화 카드 만들기 (CSS grid)

받아온 movies 데이터를 카드로 만들고, CSS의 grid를 활용하여 배치하고자 했다.

const movies_container = document.getElementById("movies-container");

//텍스트 정보가 들어가는 element 생성 함수
const createElementWithText = (elementType, className, textContent = "") => {
  const element = document.createElement(elementType);
  element.className = className;
  element.textContent = textContent;
  return element;
};

//카드 생성 함수
const createMovieCard = () => {
  if (!movies) return; // movies가 undefined인 경우 종료

  movies_container.innerHTML = ""; // 기존 카드 삭제
  movies.forEach((movie) => {
    const { title, overview, poster_path, vote_average } = movie;

    const card = document.createElement("div");
    card.className = "movie-card";

    const image = document.createElement("img");
    image.className = "poster-image";
    image.src = `https://image.tmdb.org/t/p/w500${poster_path}`;

    const titleElement = createElementWithText("h2", "title", title);
    const overviewElement = createElementWithText("p", "overview", overview);
    const voteAverageElement = createElementWithText(
      "p",
      "vote-average",
      `평점 평균: ${Math.round(vote_average)}/10`
    );

    card.appendChild(image);
    card.appendChild(titleElement);
    card.appendChild(overviewElement);
    card.appendChild(voteAverageElement);

    movies_container.appendChild(card);
  });
};

movies_container 에 영화 정보를 담은 card를 붙여주는 방식으로 작성했다.

  • append를 사용할 때는 늘 이전에 만들어진 요소들을 삭제하는 코드를 넣어주는 것이 정신건강에 이롭다...

titleElement, overviewElement 같은 영화 정보 text가 들어가는 element들을 일일히 createElement, ClassName.. 하면 반복코드가 늘어나서 함수로 뺐다.

구현 결과

페이지네이션 구현

1페이지(데이터 20개)만 가져올 수 없으니까 페이지네이션을 구현하고자 했다.

let currentPage = 1; // 현재 페이지 초기값
let startPage = 1; //시작 페이지 초기값

const page_list = document.getElementById("page-list"); //페이지 리스트 요소

const prev_button = document.getElementById("prev-button"); // `<<`버튼
const next_button = document.getElementById("next-button"); // `>>`버튼

// 영화 데이터를 받고 카드 생성하는 함수
const loadMovies = async () => {
  await fetchMovies(currentPage);
  window.scrollTo({ top: 0, behavior: "smooth" }); // 부드러운 스크롤
  createMovieCard(); // 카드 생성
  paginationFnc(totalPages);
};

// 다음 페이지 버튼 클릭 시
next_button.addEventListener("click", () => {
  startPage += 10;
  paginationFnc(totalPages);
});

// 이전 페이지 버튼 클릭 시
prev_button.addEventListener("click", () => {
  if (startPage > 1) {
    startPage -= 10;
    paginationFnc(totalPages);
  }
});

//페이지네이션 함수(페이지 번호 렌더링 및 해당 페이지의 데이터 호출)
const paginationFnc = (totalPages) => {
  const pageNumberDiv = document.createElement("div");
  pageNumberDiv.className = "page-num-div";

  const endPage = Math.min(startPage + 9, totalPages); // 총 페이지 수를 넘지 않도록

  for (let i = startPage; i <= endPage; i++) {
    const pageButton = document.createElement("button");
    pageButton.textContent = i;
    pageButton.value = i; // 버튼의 value 속성에 페이지 번호 저장

    pageButton.addEventListener("click", () => {
      currentPage = Number(pageButton.value);
      loadMovies();
      paginationFnc(totalPages);
    });

    if (pageButton.value == currentPage) {
      pageButton.style.backgroundColor = "#cdcecf";
    }

    pageNumberDiv.appendChild(pageButton);
  }
  page_list.innerHTML = ""; // 기존 버튼 삭제
  page_list.appendChild(pageNumberDiv); // 새로 생성한 버튼 추가
};

// 화면에 화 데이터 로드
loadMovies();

pageNumberDiv에 1 ~ 10 까지의 페이지 수가 뜨고, >>(이후) 버튼을 누르면 11 ~ 21, <<(이전) 버튼을 누르면 1 ~ 10이 되도록... (틈새 알고리즘)

그래서 currentPage , startPage 를 선언하고 1로 초기화해줬다.

endPagestartPage + 9로 초기화해줬다. 참고로 Math.min 함수로 startPage + 9의 값이 총 페이지수가 넘지 않도록 처리했다. (솔직히 이건 gpt의 힘, 좋은 방법이기에 앞으로 참고하려고 한다.)

반복문으로 페이지 번호 button 요소를 생성하고, 번호를 각 요소에 value값으로 설정해준다.

페이지 버튼을 클릭하면 값을 currentPage에 할당해주고 loadMovies()를 실행하도록 했다.

만약 버튼의 value값이 현재 페이지 번호와 같으면 css를 적용하여 사용자가 현재 페이지를 확인할 수 있도록 했다.

이 값을 api 요청값에 넣으면 해당 page의 movies가 넘어온다.

구현 결과

마지막으론loadMovies()로 초기 영화 데이터를 로드하게 하면

검색없는 영화 검색 팀 탄생

+ favicon 설정

필수는 아니라지만 보기 좋으니까...

<link rel="icon" href="./favicon.png" type="image/png">

index.html에 요 줄 하나만 추가하면 됐다.

팝콘 파비콘으로 약간의 귀여움을 첨가한다...

개인 과제라 화면을 미리 구상해놓지 않은 채로 작업을 하려고 하니 작업 속도가 조금 더딘게 느껴진다... 하지만 이것저것 시도해보는 장점이 있다.

과제 기본 요구사항을 다 구현하면 추가로 장르별/키워드별 필터링도 도전해보고 싶은 욕심이!!

하지만?

  • 영화 정보 디테일 모달
  • 실시간 검색
  • 북마크 기능

... 기본 요구사항이나 먼저 오류없이 완성하도록 하자 ...

profile
내배캠 React_7기 이수중

0개의 댓글

관련 채용 정보