[팀 프로젝트] 영화 검색 사이트
팀 프로젝트 소개 📖TMDB API를 이용한 영화 검색 사이트입니다.Github 주소 : https://github.com/smu06030/movie-app-team12배포 링크 : http://movie-app-team12.s3-website.ap-northeast-2.amazonaws.com/ 요구 사항 📌필수 구
mingos-habitat.tistory.com
영화 검색 사이트 개발을 진행하면서 발생한 트러블 슈팅에 대해 정리해 봤습니다.
1. TypeError: Cannot read properties of null (reading 'addEventListener')
발생한 오류 🔥
`common.js`에서 사용하는 formatMovie 메서드를 `detailPage.js`에서도 중복으로 사용하는 부분이 있어서 formatMovie 메서드를 export 하고 `detailPage.js`에서 import 하는 방식으로 사용했는데 TypeError: Cannot read properties of null (reading 'addEventListener') 에러가 발생했습니다.
const formatMovie = (movie) => ({
id: movie.id,
enTitle: movie.original_title.toLowerCase(),
koTitle: movie.title,
imgUrl: movie.poster_path,
overview: movie.overview,
rating: movie.vote_average.toFixed(2),
date: movie.release_date.slice(0, 4),
});
해결 과정 🔎
자바스크립트 파일들 간에 의존 관계 때문에 하나의 js 파일만 사용하게 되면 기능별 구조 파악이 어렵고 복잡해서 버그가 생긴 것 같은 생각이 들었습니다.
해결 방법 ✨
별도 formatMovie.js라는 파일을 만들어서 각각 import 해주는 방식으로 문제를 해결했습니다.
2. 스켈레톤 UI 백그라운드 애니메이션 렉 걸리는 현상 발생
발생한 오류 🔥
스켈레톤 UI를 사용할 때 background-position 위치를 이용해 백그라운드 애니메이션을 구현했는데 화면이 렉걸리는 현상이 발생했습니다.
해결 과정 🔎
개발자 도구에서 요소 검사로 해당 UI가 어떻게 움직이는지 확인을 해 본 결과 `background-position` 속성을 사용하게 되면 배경 이미지의 위치를 조정하기 때문에 렌더링 과정에서 페인트 단계를 다시 수행하는 문제가 있었습니다. 결국 성능 저하 문제가 생길 수 있습니다.
개발자도구 -> performance -> Event Log
`background-position`을 사용했을 때 paint 단계를 계속 수행하는걸 확인하실 수 있습니다.
`transform`을 사용했을 땐 paint 단계를 로딩 시 스켈레톤 UI를 띄울 때 수행하고 이후에는 paint 단계를 수행하지 않는 걸 볼 수 있습니다.
해결 방법 ✨
`transform` 속성은 시각적 위치만 바뀌고 실제 DOM 위치는 변경되지 않기 때문에 레이아웃과 페인트 단계를 건너뛸 수 있어서 성능적인 측면에서 매우 효율적입니다.
.skeleton::after{
transform: translateX(-100%);
animation: load 1s infinite;
}
@keyframes load {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
3. 이미지 로딩 속도
발생한 오류 🔥
로딩이 완료된 후에 이미지를 한 번에 보여주기 위해 스켈레톤 UI를 사용했는데 UI가 사라지고 난 후 이미지가 한 번에 보이는 게 아니라 각각 이미지 로딩이 다르게 보여서 사용자 경험 측면에서 불편한 느낌이 들었습니다.
해결 과정 🔎
처음엔 아래와 같은 방식으로 진행했었는데 이미지 로딩 속도는 다 다르게 보였습니다.
// 데이터가 로드되면 스켈레톤 UI 보임
skeletons.style.display = 'flex';
movieDetails.style.display = 'none';
// 데이터가 로드되면 스켈레톤 UI 숨김
skeletons.style.display = 'none';
movieDetails.style.display = 'flex';
❗여기서 문제 스켈레톤 UI만 숨길뿐이지 이미지가 로딩이 됐는지 확인할 수 있는 방법이 없었습니다.
그래서 이미지가 로딩이 됐는지 확인할 수 있는 함수를 따로 만들었습니다.
해결 방법 ✨
`img` 모든 태그를 가져와 로딩이 완료가 됐는지 태그 요소를 하나씩 접근해 완료가 이미 되어 있으면 `imageLoadHandler` 메서드를 호출하고 아니라면 load, error 이벤트가 생겼을 때 콜백함수로 `imageLoadHandler`를 실행하게 작성했습니다.
images.forEach((image) => {
if (image.complete) {
imageLoadHandler();
} else {
image.addEventListener("load", imageLoadHandler);
image.addEventListener("error", imageLoadHandler);
}
});
`imageLoadHandler` 메서드에서 로딩, 실패한 이미지의 개수를 전체 이미지 개수와 비교해 모든 이미지가 로딩이 됐다면 `callback` 함수를 실행하게 코드를 작성했습니다.
const imageLoadHandler = (event) => {
if (!event) {
loadedCount++;
} else {
if (event.type === "load") {
loadedCount++;
} else {
failedCount++;
}
}
if (loadedCount + failedCount === totalImages) {
callback();
}
};
모든 데이터가 로딩 됐다면 아래 스켈레톤 UI를 숨겨주는 콜백함수를 실행시키도록 작성했습니다.
checkImagesLoaded(() => {
// 데이터가 로드되면 스켈레톤 UI 숨김
selectors.skeletonDetail.style.display = "none";
selectors.movieDetails.style.display = "flex";
selectors.skeletonActor.style.display = "none";
selectors.actorContainer.style.display = "flex";
});
전체 코드입니다.
checkImagesLoaded(() => {
// 데이터가 로드되면 스켈레톤 UI 숨김
selectors.skeletonDetail.style.display = "none";
selectors.movieDetails.style.display = "flex";
selectors.skeletonActor.style.display = "none";
selectors.actorContainer.style.display = "flex";
});
// 모든 이미지 로드 완료 확인
const checkImagesLoaded = (callback) => {
const images = document.querySelectorAll("img");
let loadedCount = 0;
let failedCount = 0;
const totalImages = images.length;
const imageLoadHandler = (event) => {
if (!event) {
loadedCount++;
} else {
if (event.type === "load") {
loadedCount++;
} else {
failedCount++;
}
}
if (loadedCount + failedCount === totalImages) {
callback();
}
};
images.forEach((image) => {
if (image.complete) {
imageLoadHandler();
} else {
image.addEventListener("load", imageLoadHandler);
image.addEventListener("error", imageLoadHandler);
}
});
};
깨달은 점 ❕
1. background-position vs transform: translate 차이점
`transform`은 시각적 위치만 바뀌고 실제 DOM 위치는 변경되지 않기 때문에 레이아웃과 페인트 단계를 건너뛸 수 있어서 성능적인 측면에서 매우 효율적입니다.
`background-position`은 배경 이미지의 위치를 조정하기 때문에 페인트 단계를 다시 수행해야 해서 성능 저하 문제가 생길 수 있습니다.
회고 🧐
코드를 작성할 때 렌더링 측면에 대해서 크게 생각해 본 적이 없었던 것 같은데 이번 프로젝트를 계기로 사용자 경험 측면에 대해서도 생각할 수 있어서 좋은 경험이었습니다!

'공부 > 트러블 슈팅' 카테고리의 다른 글
[트러블 슈팅] 카카오맵 API를 활용한 캠핑장 데이터 렌더링 최적화 (0) | 2024.09.19 |
---|---|
[트러블 슈팅] SVG 컴포넌트 재사용 시 발생한 이미지 중복 문제 해결하기 (0) | 2024.09.15 |
[트러블 슈팅] MBTI 테스트 (Glitch에서 JSON-server의 응답 속도 차이) (1) | 2024.09.11 |
[트러블 슈팅] 방콕 스타일 (새로고침 시 좋아요 랜덤 활성화 이슈) (1) | 2024.09.02 |
[트러블 슈팅] 방콕 스타일 (supabase RLS 오류) (0) | 2024.09.02 |
[트러블 슈팅] 포켓몬 도감 (svg, router, 이벤트 버블링) (0) | 2024.08.26 |
[트러블 슈팅] styled-components 오류 (표준, 비표준 속성) (2) | 2024.08.16 |
[트러블 슈팅] 커스텀 훅의 업데이트가 반영되지 않을 때 (0) | 2024.08.13 |