Spotify Clone 프로젝트(UI) : Copytify

Dongkyu·2025년 6월 8일
post-thumbnail

프사친(프론트엔드 사자친구) 팀 프로젝트

Spotify 클론 코딩 Ui 프로젝트
목표 : 스포티파이의 클론 코딩을 통해 배웠던 HTML/CSS 지식을 사용하고 몰랐던 지식을 습득하는 것을 목표로 합니다.
선정 이유
1. 공통의 관심사와의 연계: 팀원 모두의 취향과 맞닿아 있어 작업 동기와 팀 시너지를 극대화할 수 있다고 판단했습니다.
2. UI 중심의 구조: Spotify는 깔끔하고 직관적인 단색 기반의 디자인을 가지고 있으며, 과도한 JavaScript 없이도 CSS와 HTML만으로 충분히 표현 가능한 구조를 갖추고 있어, UI/UX 중심의 프론트엔드 개발 학습 목적에 적합했습니다.

배포 URL : https://frontend-lion-friends.netlify.app/
Github Repo URL : https://github.com/FRONTENDBOOTCAMP-14th/ui-project-team-10
해당 페이지는 교육용 목적으로 제작되었으며, 상업적 사용이 제한되어있음을 명시합니다.



나의 역할

Home Header

Spotify 웹의 상단 헤더 영역을 고정(fixed) 위치로 구현하였으며, 다음과 같은 기능 및 반응형 동작을 적용했습니다.

  • 검색창 토글 기능: 돋보기 아이콘 클릭 시 원형 버튼으로 축소되며, 다시 클릭 시 검색창이 확장되어 돋보기 아이콘 + 입력 필드 + 둘러보기 버튼이 표시되는 토글 방식으로 구성했습니다.

  • 네비게이션 메뉴 구성: Premium, 지원, 다운로드, 앱 설치하기, 가입하기, 로그인하기 등 6개의 메뉴를 제공하며, 그 중 앱 설치 / 회원가입 / 로그인 페이지는 직접 구현하였습니다.

  • 미디어쿼리 기반 반응형 UI (4단계)

    • width < 850px: 메뉴 항목 일부 숨김, 햄버거 메뉴로 전환
    • 850px ≤ width < 1078px: 지원, 다운로드, Premium 메뉴가 다시 표시됨
    • 1078px ≤ width < 1742px: 검색창이 기본 확장 상태로 유지되며, 축소되지 않음
    • 1742px 이상: 검색창이 가운데 정렬됨
  • 접근성 개선: 각 네비게이션 버튼에 title 속성을 부여하여 마우스 오버 시 기능 설명이 표시되도록 구현

작업 과정

1. 컴포넌트화
  • <header> 태그를 기준으로 내부에 사용되어지는 컴포넌트의 구분작업을 우선적으로 진행하였습니다.
  • markup작업 진행
    각 컴포넌트를 <div>로 묶고 @media에서 작동되는 기준으로 각 컴포넌트를 마크업작성을 하였습니다.
  • 컴포넌트 작업이 완성후 컴포넌트 페이지를 구성하여, 각 컴포넌트의 재활용성을 높히기 위하여 복사할 수 있는 기능을 추가하였습니다.
  • 이후 각 컴포넌트의 조립을 통해 <header> 컴포넌트 즉 해더컴포넌트를 작업하였습니다.
2. 어려웠던 점
  • <header>에서 @media의 동작이 총 5단계로 구성이 되어 있었으며, JavaScript의 사용이 필요로한 기능이 있었기에, 기능을 추가하는 과정에서 어려움을 격었습니다.

  • 특히 검색창은 토글기능을 통해 축소 확장이 어려웠습니다.
    검색창

    • 축소상태의 검색창과 확장상태의 검색창 두개의 컴포넌트를 각각 만듭니다.
    • 축소상태에서는 확장검색창 컴포넌트에 .hiden클래스를 부여하여 숨김처리하며, 확장상태에서는 축소상태의 컴포넌트에 .absolute클래스를 부여하는 방식으로 기능을 제작하였습니다.

문제점

  • 축소 및 확상시에 각각의 클래스를 부여하도록 JavaScript의 구현과정에서 어려움을 겪었습니다.
  • 또한 위 기능은 브라우저의 1078px 이하에서만 작동이 되어야하며, 1078px 이상에서는 확장되어있는 검색창 상태만 존재되어야 했습니다.
    이를 해결하기 위해 window.visualViewport.width를 사용하여 현제 상태의 브라우저 너비를 인식하고, 이하일 경우에 클릭 이벤트를 주어 작동되도록 코드를 작성하였습니다.
  • 기본상태에서는 확장상태의 검색창(extendSearchBar)에 hidden 클래스를 적용해놓아 클릭시 hidden클래스는 사라지며, 동시에 축소상태의 검색창(clossingSearchBar)에 absolute클래스가 추가됩니다.
toggleSearchBtn.addEventListener("click", function () {
  
  // 브라우저 너비 확인
  const viewportWidth = window.visualViewport.width;
  
  // test
  console.log("click");
  
  // 브라우저 너비 1078px 미만일경우
  if (viewportWidth < 1078) {
    // 확장상태 검색창 -> hidden 클래스 토글추가
    extendSearchBar.classList.toggle("hidden");
	// 축소상태 검색창 -> absolute 클래스 토글 추가
    clossingSearchBar.classList.toggle("absolute");
    // 확장시 input에 커서 집중
    searchInput.focus();
  } else {
    searchInput.focus();
  }
});


🟢 Preview Box (하단 미리듣기 광고 박스)

페이지 하단에 고정된(fixed) 미리듣기 광고 박스를 구현했습니다. 이 요소는 Spotify 비로그인 사용자에게 표시됩니다.

  • 반응형 높이 조정: 광고 문구의 줄 수에 따라 동적으로 높이가 증가하며, min-height, max-height 속성을 설정해 안정적인 UI 유지
  • 시각적 강조 디자인: 알약 모양의 흰색 배경 버튼에 검은색 텍스트를 사용하였으며, hover 시 스케일을 1.05로 확대하는 애니메이션 효과를 추가
  • 기능 연결: "무료 가입" 버튼 클릭 시 회원가입 페이지로 이동
  • 플레이어 전환 구조 구현: 실제 Spotify처럼 로그인 시 플레이어 박스로 변경되도록 UI를 설계했지만, 로그인 기능은 미구현 상태

작업 과정

  • 페이지 구성에서의 광고창은 하단에 fixed 상태였기에 <footer> 태그가 가시적으로 맞는 것 같으나, 의미적으로는 옳지 못하다는 판단이 되어 <div> 마크업으로 전체 컴포넌트를 구성하였습니다.
  • 제작 과정에서 기본적인 버튼컴포넌트, 글꼴, 색 등을 정리해두어 재사용하는 과정이었기에 큰 어려움 없지 제작 할 수 있었습니다.


🟢 Login Page (로그인 페이지)

Spotify 스타일의 로그인 페이지를 구현하였으며, 기본적인 유효성 검사 및 반응형 UI를 적용했습니다.

  • 반응형 구성: 767px 이하 해상도에서 input 요소가 전체 너비를 차지하도록 조정하여 모바일에서도 적절히 작동
  • 간단한 유효성 검사 적용:
    • type="email"과 required 속성을 통해 형식 검사
    • :user-invalid CSS 선택자를 활용하여 유효하지 않은 입력 시 빨간색 경고 메시지 출력 및 input 테두리 색상 변경
  • 가입 링크 연결: Copytify에 가입하기 링크를 회원가입 페이지와 연결
기억나는 점
  • 반응형이기에 모바일환경과 브라우저환경을 나누어 작업을 하였습니다. 처음 작업할 때에는 모바일을 기준으로 작업하였으며, 확장상태를 @media로 작업하였습니다.

  • input에 사용되는 기능이 많은지라 많은 디자인코드가 사용되었습니다.

    • hover : 선의 색이 흰색으로 변합니다.

    • focus : outline이 두꺼워지며, 색은 흰색입니다.

    • 유효성에 옳지 못할 경우 빨강색선으로 변합니다.

    • .error-massage : display가 none인 <p> 태그에 :user-invaild선택자를 이용하여, 유효성검사에 오류가 발생할 경우 display에 block을 주어 경고문을 출력합니다.

.error-massage {
          height: 20px;
          align-content: center;
          color: #f3727f;
          font-weight: 400;
          font-size: 0.8125rem;
          margin-block-start: 8px;
          display: none;

          svg {
            vertical-align: bottom;
          }
        }

        input {
          border: 1px solid var(--spotify-gray);
          width: 100%;
          height: 48px;
          border-radius: 4px;
          padding: 12px;
          color: var(--spotify-white);

          &::placeholder {
            font-weight: 600;
          }

          &:hover {
            border-color: white;
          }

          &:focus {
            outline: 3px solid white;
          }

          &:user-invalid:focus {
            outline: 3px solid rgb(237, 44, 63);
            border: none;
          }

          &:user-invalid {
            outline: 1px solid rgb(237, 44, 63);
            border: none;
          }

          &:user-invalid + .error-massage {
            display: block;
          }
        }
      }


🟢 Sign Page (회원가입 페이지)

로그인 페이지에서 연결되는 회원가입 페이지입니다. UI 위주의 구성에 집중하였으며 다음과 같은 특징이 있습니다.

  • 유효성 검사 적용: 로그인 페이지와 유사하게 input 요소에 대해 기본적인 유효성 검사와 시각적 피드백 제공
  • 소셜 가입 버튼 UI 구현: Google, Facebook, Apple 버튼 디자인 구현
    • Spotify 스타일에 맞게 제작된 재사용 가능한 버튼 컴포넌트를 활용
    • 회색 테두리와 좌측 아이콘, 중앙 텍스트로 구성되며, hover 시 테두리가 흰색으로 변경
기억나는 점
  • 시간부족의 관계로 로그인페이지의 css를 많이 이용하였습니다. 거의 마크업 구조만 조금 변경하여 비슷하게 제작되었습니다.
  • 원래 2단계의 @media가 존재하나, 기능을 추가하지 못하였습니다.
  • 대부분의 기능은 로그인 페이지와 동일합니다.


🟢 App Install Page (앱 설치 페이지)

Spotify 앱 설치 유도 페이지로, 실제 Microsoft Store 또는 앱 다운로드 링크를 통해 브라우저 기반 설치 기능이 동작하도록 구성했습니다.

  • 반응형 레이아웃 전환:
    • width ≥ 1387px에서는 flex-direction: row로 가로 배치
    • width < 1387px에서는 flex-direction: column으로 전환되는 구조
  • 앱 설치 기능 연결: Microsoft Store 또는 브라우저 다운로드 링크 연결을 통해 앱 설치 기능 유도
  • SPA(Single Page Application)로 전체 페이지를 고치지 않고, 필요한 부분만 동적으로 교체/렌더링하는 방식의 웹 어플리케이션 구조로 제작 하였습니다. (해당 링크의 실제 동작 방식은 개발자도구 기반 분석을 통해 확인)
기억나는 점
  • SPA이기에 JavaScript를 이용해야했습니다.
<div class="main-component">
  ...
<div />
  • 상단의 앱 설치하기 링크를 클릭 시 <div> 태그의 내부에 제작된 컴포넌트를 불러왔습니다.
async function loadPage() {
  // 현재 주소창에서 해시값을 가져옴. 예: #install-page
  const hash = location.hash;

  // 해시가 없으면 → 기본 페이지 (index.html의 <main>) 그대로 둠
  if (!hash) return;

  const pageName = hash.substring(1); // '#' 제거: "install-page"

  try {
    const response = await fetch(`./${pageName}.html`);
    const html = await response.text();
    document.querySelector(".main-component").innerHTML = html;
  } catch (error) {
    document.querySelector(".main-component").innerHTML =
      "<p>페이지를 불러올 수 없습니다.</p>";
  }
}

window.addEventListener("hashchange", () => {
  location.reload();
});

// 해시가 바뀌었을 때만 실행 (ex. #about, #contact 클릭)
window.addEventListener("hashchange", loadPage);

// 페이지 처음 열었을 때 실행
window.addEventListener("DOMContentLoaded", loadPage);

배운점 & 느낀점

1) 계획의 중요성

  • 초반에 컴포넌트의 구분과, 제작계획 등을 세울 때에 성급한 마음에 정확하게 나누지 못하고 제작을 했었습니다. 처음에는 진행속도가 붙어 빠르게 제작되는가 싶었으나, 수정사항이 생기고, 서로의 코드를 병합하는 과정에서 충돌등의 문제가 발생하였습니다.

    배운점
    다음부터는 느리더라도, 컨벤션, 요구사항, 목표, 역할 분담, 구현기능 등을 세분화하여 각각 분업화하고 팀원간의 규칙을 정하는데에 더 많은 시간을 사용할 것입니다. 이것이 초반에는 불편하고 시간이 걸릴지라도, 프로젝트를 진행하면서 생기는 문제점들을 보안할 수 있는 어쩌면 가장 빠른 길이라고 생각됩니다.

  • UI프로젝트를 진행하면서 눈에 보이는 부분에 집중을 하다보니 사용자적 개발을 하지 못한것 같습니다.

    배운점
    디자인 시안을 제작 할 때에는 원소단위부터 큰컴포넌트까지 세분화하여 제작하고, 자주사용되어지는 치수 및 기능들을 묶어 공통으로 사용 할 수 있도록 기능을 추가해보는 작업도 좋을 것 같습니다.

2) 성능의 개선

  • 커밋메시지를 작성할 때 규칙을 정하였으나, 소통의 오류에서인지 조금씩 다른 규칙으로 커밋을 작성했던 것을 발견하였습니다. 때문에 어떠한 부분이 수정되더라도, 서로 작성된 글의 내용이 다르다보니 이해하는데에 많은 시간이 걸렸고 이는 작업에 효율적이지 못한 영향을 받았습니다.

    배운점
    대표적인 기능으로 feat:, fix:, refactor: 등의 커밋메시지를 적극 활용하여, 각각의 커밋이 어떠한 역활을 하고 있는지 직관적으로 이해할 수 있도록 세분화 할것입니다.

  • Pull Request를 진행 할 때에 시간이 부족하고, 귀찮음에 코드리뷰를 정확하게 진행하지 못했던 경험이 있었습니다. 이로 큰 문제가 발생하지는 않았지만, 이러한 PR이 누적되면서 각자의 코드에 개성이 보이는? 즉 서로 다른 규칙으로 코드를 작성하는 상황이 생기게 되었습니다.

    배운점
    하루에 계획된 작업이 끝나고 PR의 시간을 정해 함께 코드 리뷰 등 서로 작성한 코드가 어떻게 작성이 되었고, 어떻게 개선하면 더 좋을지를 의논하는 시간을 만들 필요가 있다고 생각됩니다.

3) 협업의 필수 능력

  • 프로젝트를 한다는 것은 함께 작업을 하는 것이기에, 프로젝트 기간에는 그 누구보다도 서로 의사소통이 잘 되어야한다고 생각됩니다. 사소한 문제라도 의견이 다르고 추구하는 방향이 다르면 나타나는 결과물에 큰 차이가 나타나는 문제가 있었습니다.

    배운점
    이러한 문제를 해결할 수 있는 유일한 방법은 서로의 생각을 이야기하고 의견을 수립하는 것이라고 생각됩니다.
    생각이 다름을 이해하고, 사소한점이라도 이야기하고 말하는 습관을 가져야 할것 같습니다. 나에게는 중요하지 않다고 생각되는 부분이라도 함께 작업하는 팀원에게는 중요한 사안이 될


    수도 있기 때문입니다.

profile
인생의 모든날을 사랑하고 즐기자

0개의 댓글