image1

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

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

위 청첩장 주소는 아카이브로 남겨놓은 페이지입니다. 결혼식이 끝난 현재 방명록 추가 & 삭제 기능은 막아두었으며, 전화번호, 계좌번호 등의 개인정보는 제외한 상태입니다. 사진 또한 현재 AI 생성 사진으로 대체되었으나, 당시에는 저희의 실제 웨딩 사진이 사용되었습니다.

기능 구현

이름은 “모바일” 청첩장이지만 사실은 웹페이지입니다. 주로 모바일로 링크를 공유하기에 모바일 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로만 배포할 계획이었습니다. 그러나 상태 관리 서버를 위해 클라우드 배포가 필요했습니다. 비용을 최대한 줄이기 위해 저는 Naver Cloud Platform (NCP) 신규 가입 3개월 무료 크레딧을 이용하기로 합니다.

처음에는 Github Pages로 배포된 페이지에서 그냥 ec2 주소로 직접 호출하면 되지 않을까 했는데, Mixed Content Policy에 의해 https 페이지에서 http 요청을 차단하는 것이었습니다. 따라서 도메인을 구매하고, SSL 인승서까지 등록해야 했습니다. 구축 과정은 아래와 같습니다.

  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의 Root CA가 브라우저에 탑재되기 시작한 시기는 2022년 11월로 다른 메이저 CA 기관에 비해 상대적으로 최근에 등록된 기관입니다. 2022년 11월 이후 브라우저를 업데이트하지 않은 경우 보안에 의해 접근이 차단될 수 있습니다.

실제로 모바일 청첩장을 전달받은 극히 일부 지인에 의해 이런 문제가 발생했습니다. 이들은 공통적으로 2022년 11월 이전의 핸드폰 기종을 사용했습니다. 모든 사용자가 다 최신 업데이트를 하지는 않기에 이런 문제가 발생한 것으로 보입니다.

후기

사이드 프로젝트가 실제로 활용되지 않으면 큰 의미가 없다고 생각했기에 활용도에 있어서 상당히 의미 있었습니다. 양가 지인들을 생각하면 꽤 많은 사람들에게 공유되었으며, 긍정적인 피드백을 많이 받았습니다. 물론 SSL 인증과 같이 아주 매끄럽지 않았던 경우도 있었습니다만, 다행히 극히 소수였고, 다른 방법으로 예식 정보 전달이 가능했습니다.

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

댓글남기기