마지막 포스팅으로부터 1년이 넘었습니다. 그 사이 저는 결혼을 하게 되었고, 준비 과정에서 특별한 기억을 남기고 싶었습니다. 개발자이기 때문에 할 수 있는 모바일 청첩장 개발을 직접 해보기로 결심합니다.

완성된 모바일 청첩장 바로가기

위 청첩장 주소는 아카이브로 남겨놓은 페이지입니다. 실제 사용 당시에는 다른 도메인을 사용하였으며, 방명록 추가 & 삭제 기능은 막아두었습니다.

개발 과정

이름은 “모바일” 청첩장이지만 사실은 웹페이지입니다. 주로 모바일로 링크를 공유하기에 모바일 viewport에 최적화되어 있죠. 사용된 기술 스택은 아래와 같습니다.

React JS Go TypeScript Sass Nginx SQLite

위의 기본 구성을 제외하고는 최대한 외부 라이브러리 없이 직접 구현하였습니다.

반응형

주위에서 받은 모바일 청첩장에서 화면 크기에 따라 레이아웃이 틀어지는 경우를 많이 봤습니다. 그래서 저는 개발 초기부터 길이 단위의 기준을 정했습니다. rootfont-size를 viewport width에 따라 동적으로 선언하고, 그 외의 모든 요소에서 길이를 정의할 때 rem 단위만 사용하는 것으로 통일했습니다. 즉, font-size이면서 모든 길이의 기준이 되는 겁니다.

:root {
  // viewport width가 500px 이하로는 4vw
  // 500px을 초과하는 경우 20px로 고정
  font-size: 20px;
  @media (max-width: 500px) {
    font-size: 4vw;
  }
}

// 이후 다른 요소의 길이를 지정할때 rem 단위를 사용한다.
// 예시:
.content {
  padding: 0 0.7rem 0.7rem 0.7rem;
  width: 20rem;
}

캐러셀

캐러셀은 청첩장의 Gallery 부분에 들어갑니다. 캐러셀의 회전은 이미지 양쪽 끝 또는 아래 지정된 순서를 클릭하거나 옆으로 드래그하는 경우 일어납니다. 드래그를 구현하기 위해 onclick, mousemove, mouseup (모바일의 경우 touchmove, touchend) 이벤트를 사용합니다. 회전 완료 시에 일어나는 자연스러운 마무리는 animation, keyframes, transition으로 구현하였습니다.

$gallery-width: 21rem;
$transition-duration: 0.3s;

@keyframes moving-right {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(calc($gallery-width - 100%));
  }
}

.carousel-list {
  &.transitioning {
    transition: $transition-duration ease-out;
  }

  &.moving-left {
    animation-name: moving-right;
    animation-duration: $transition-duration;
    animation-fill-mode: forwards;
    animation-direction: reverse;
  }

  &.moving-right {
    animation-name: moving-right;
    animation-duration: $transition-duration;
    animation-fill-mode: forwards;
  }
}

모바일에서는 좀 더 고려할 부분이 있습니다. 캐러셀을 옆으로 드래그하는 동안 화면이 위아래로 스크롤이 되면 안 됩니다. 그와 동시에 위아래로 스크롤하는 동작을 캐러셀이 방해해서도 안됩니다. 즉, 사용자가 드래그하는 것이 화면 스크롤을 위한 것인지, 아니면 캐러셀 회전을 위한 것인지 판단해야 합니다. 이를 위해 Drag Sensitivity를 지정하였습니다.

const DRAG_SENSITIVITY = 15;

const onTouchMove = (e: TouchEvent) => {
  const xMove =
    e.targetTouches[0].clientX - dragOptionRef.current.startingClientX;
  const yMove =
    e.targetTouches[0].clientY - dragOptionRef.current.startingClientY;
  if (Math.abs(xMove) > DRAG_SENSITIVITY) {
    // Drag Sensitivity 만큼을 x축으로 이동하면 캐러셀 회전 및 화면 스크롤 방지
  } else if (Math.abs(yMove) > DRAG_SENSITIVITY) {
    // Drag Sensitivity 만큼을 y축으로 이동하면 화면 스크롤
  }
};

꽃잎 배경

꽃잎이 떨어지는 배경은 canvas로 구현하며, position: fixed를 사용합니다. 단, 배경이 가장 위에 있는 경우 버튼 클릭 이벤트가 동작하지 않으니 클릭 이벤트가 있는 경우 배경 위에 위치하도록 z-index를 잘 설정해주어야 합니다.

네이버 지도

Location 영역의 지도는 네이버 지도 API를 사용합니다. Naver Cloud Platform에 가입하면 사용 가능하며, 무료 사용 시 월 이용 횟수에 제한이 있습니다. (웬만큼 큰 서비스가 아니라면 한도를 넘어가는 일은 없습니다.)

방명록

방명록 기능을 위해서는 방명록 글을 저장할 서버가 필요합니다. 이를 위해 Go와 SQLite로 구성된 간단한 CRUD 서버를 구축하였습니다. 많은 트래픽이 예상되는 서버는 아니기에 이 정도만으로 충분하다고 판단했습니다.

클라우드 배포 및 SSL 인증

초기에는 비용이 전혀 없는 Github Pages로 배포할 생각이었으나, Go 서버가 있기에 정적 배포가 아닌 클라우드 배포를 선택했습니다. 그래서 저는 Naver Cloud Platform 신규 가입 3개월 무료 크레딧을 이용하기로 합니다. 구축 과정은 이렇습니다.

  1. Micro g3 Server에 Nginx로 프론트엔드와 백엔드 서비스 환경 구축
  2. 가비아에서 도메인 구매
  3. Certificate Manager에서 구매한 도메인으로 인증서 신청
  4. Global DNS에서 도메인 추가 후 CNAME 레코드 추가하여 인증서 검증
  5. 인증서가 적용된 Load Balancer 생성
  6. Global DNS 도메인에서 생성한 Load Balaner를 A 레코드로 추가

후기

사이드 프로젝트가 실제로 활용되지 않으면 큰 의미가 없다고 생각했기에 활용도에 있어서 상당히 의미 있었습니다. 양가 지인들을 생각하면 꽤 많은 사람들에게 공유되었으며, 긍정적인 피드백을 많이 받았습니다.

그러나 문제점 또한 있었습니다. SSL 인증서 관련 문제였습니다. 인증을 정상적으로 완료했음에도 일부 지인들의 기기에서 서버의 신원을 확인할 수 없다는 메시지가 나왔습니다. 이 문제가 나타난 사람들의 공통적인 특징은 오래된 핸드폰 기종이었습니다. 인증받은 CA 기관이 사용자의 브라우저에도 탑재되어야 하는데, NCP의 RootCA가 브라우저에 탑재된 시기를 생각하면 충분히 생길 수 있는 문제였습니다. 예상하지 못했던 문제였으며, 이로 인해 SSL 인증 과정에 대해 한번 더 배워갑니다.

비용 절감 측면에서는 아무 효과가 없었습니다. 업체를 활용하더라도 대부분 비용 거의 없이 제작이 가능합니다. 물론 비용 절감이 목적은 아니었기 때문에 크게 신경 쓰진 않았습니다. 단지 직접 만든 청첩장을 아카이브로 남겨두어 소중한 기억으로 간직하기 위함이었습니다.

댓글남기기