사이드 프로젝트는 사이드가 아니다

Mar.12.2024 장해민

Web Frontend

사이드 프로젝트는 새로운 디자인과 기술을 시도하고, 검증하고, 시행착오를 겪을 수 있는 정말 좋은 방법의 하나입니다. 거듭된 실수와 실패의 경험으로 더 성공적인 결과를 만들 수 있게 해주는 수많은 사이드 프로젝트야말로 더 나은 세상을 만들기 위한 메인 프로젝트가 아닐까, 생각합니다.

저는 틈틈이 회사 안팎으로 사이드 프로젝트를 만들고 있는데요, 여기 몇 가지 작업을 소개합니다.

우아한 웹 툴킷 – WOOWACON 2023 발표 영상

GitLab, Jira, Wiki 등 사내 웹 서비스들을 조금 더 효과적으로 사용하기 위해 만들었고, 현재 우아한형제들 구성원의 절반 가까이 이용 중인 브라우저 익스텐션입니다.

플래닝 포커 – GitHub

시중에 있는 플래닝 포커 서비스의 문제점을 보완하고 광고 없이 무료로 사내 업무에 활용하기 위해 만든 실시간 웹 애플리케이션입니다.

Interop – GitHub

InterNoto Sans KR을 조합한 폰트입니다. WOOWACON 2023 홈페이지에 사용되었습니다.

Radix Studio – GitHub

만다오(프로모션용 웹 앱 빌더)를 오픈소스로 만들지 못한 아쉬움에 비슷하지만 전혀 다른 방식으로 개발한 오픈소스 WYSIWYG 에디터입니다.

사이드 프로젝트는 자율성이 높고 본업과 균형을 맞춰야 하기 때문에 끝까지 완수하려면 충분한 시간과 의지가 필요합니다. 그래서 더 나은 완결성과 완성도를 위해서는 목표를 작게 잡는 것이 유리합니다.

이 글에서 소개할 everymoji.com 또한 작은 목표를 두고 시작했습니다.

everymoji(에브리모지)는 동료 개발자 김하루 님과 함께 만든, 텍스트 애니메이션 이모티콘을 순식간에 만들어주는 웹 애플리케이션입니다. 텍스트를 입력하고 몇 가지 옵션만 선택하면 GIF 이미지를 생성해 주죠. 다운로드한 이미지는 Slack, Discord 등의 메신저에 커스텀 이모티콘으로 등록하여 사용할 수 있습니다.

everymoji.com

하루 님은 만다오 팀에서 인상적인 디테일과 결과물을 보여줬는데요, 디자인과 애니메이션에 관심이 많고 사소해 보일 수 있는 부분까지 놓치지 않는 점이 저와 비슷합니다.

하루 님이 만다오에서 디자인하고 개발한 애니메이션

마음껏 상상하고 창조할 수 있는 기회가 있다면 정말 재밌게 할 것 같았고 역시나 타고난 역량을 아낌없이 발휘해 주었습니다. 취향과 고민 포인트가 잘 맞아서인지 마치 한 사람이 개발하듯 자연스러웠다는 점도 색다른 경험이었습니다.

휴일에도 쉬지 않고 끝까지 재밌게 함께한 하루 님께 감사하다는 말씀 전하며 사이드 프로젝트로 everymoji 만든 이야기, 가볍게 적어보겠습니다.

Slack 커스텀 이모티콘

우아한형제들의 사내 메신저는 Slack입니다. 채널, 스레드, 허들 등 처음 사용할 땐 낯설었지만 지금은 없으면 안 되는 강력한 기능들이 많은데요, 그중에서도 매일매일 가장 많이 사용하는 건 단연 이모티콘입니다. 이모티콘은 단순한 리액션뿐만 아니라 감정 표현, 투표, 자동화 등 다양한 방식으로 업무에 활용되고 있습니다.

Slack은 기본 탑재된 유니코드 이모지 외에도 사용자가 직접 이미지를 업로드하여 사용할 수 있는 커스텀 이모티콘 기능도 제공합니다. 커스텀으로 등록된 이모티콘은 워크스페이스 사용자 누구나 사용할 수 있습니다.

Slack 이모티콘 선택 화면

사진이나 그림 이모티콘은 가지고 있는 파일을 바로 업로드하면 되니 비교적 간단히 등록할 수 있습니다. 크기를 조절하려면 특정 부분을 잘라내거나 새롭게 캡처하면 됩니다.

하지만 텍스트 기반의 이모티콘을 만드는 건 상대적으로 번거롭습니다. 컴퓨터에 폰트도 설치해야 하고 텍스트 크기와 간격, 정렬을 조절하고 배경과 텍스트 색상을 설정하는 등 일련의 의사 결정과 편집 과정이 필요합니다. 특히 긴 단어나 문장은 한 프레임에 담으면 가독성이 좋지 않아서 애니메이션으로 만들어야 합니다. 애니메이션은 적절한 도구와 웬만큼 기술이 있는 사람들 즉, 우리가 흔히 말하는 “능력자”만이 만들 수 있죠.

누구나 쉽게 등록하고 사용할 수 있는 Slack 커스텀 이모티콘을 누구나 쉽게 만들 수도 있어야겠다는 생각이 들었습니다. 신규 입사자를 환영하거나 재치 있게 리액션 하고 싶을 때, 빠르게 애니메이션 이모티콘을 만들어 사용할 수 있다면 정말 재밌을 것 같았습니다.

디자인

everymoji는 궁극적으로 “이모티콘을 만든다”라는 단 하나의 목적을 가진 간단한 애플리케이션입니다. 설명서 없이도 이해할 수 있을 만큼 쉽고 간결하면서도 탄탄하고 견고한 느낌이 들게 만들고 싶었습니다. 모두를 만족시키는 디자인은 불가능하지만, 대부분 사람을 만족시킨 디자인을 흉내 내는 것은 어렵지 않습니다. 이것이 바로 우리가 디자이너가 아니어도 디자인을 할 수 있는 비결입니다. 12음계만으로 새로운 음악이 계속 탄생하듯 디자인도 작은 변화로 전혀 다른 결과를 만들어 낼 수 있습니다.

Neumorphism(뉴모피즘)에 청명함을 더하고 차갑지만 포근한 색을 입혔습니다. 그리고 우리의 눈에 만족스러워 보일 때까지 다듬었습니다. 만족스럽다는 것은 매우 추상적이고 상대적이기 때문에 설명하기 어렵지만, 설명할 수 있는 디자인이 설명하지 못하는 디자인보다 더 인정받는다는 것을 많이 경험했습니다. 아마도 저는 앞으로 시간이 지나도 촌스러워 보이지 않을 만큼 느껴질 때까지 다듬은 것이라 믿고 있습니다. 여러분들이 보기에 어떤가요? 마음에 드시나요?

하루 님이 한 땀 한 땀 CSS로 작성한 애니메이션

분명히 이 디자인이 좋을 수도, 그렇지 않을 수도 있습니다. 사람은 누구나 저마다의 디자인 감수성을 타고나기 때문이죠. 우리의 삶은 알게 모르게 많은 순간 디자인에 영향을 받고 있습니다. 그 기준은 서로 다르겠지만 나에게 아름답다고 느껴지는 무언가를 봤을 때 기분이 좋아지는 건 한 번쯤 경험해 봤을 겁니다. 아름다우면 기분이 좋고 기분이 좋으면 재미가 있습니다. 제가 사이드 프로젝트를 지속할 수 있는 원동력입니다.

반응형 디자인

기술

everymoji는 사용자가 입력한 정보를 Canvas API로 캔버스에 먼저 렌더링하고, 저장할 때 미리 보기에 사용된 프레임들을 묶어서 gif.js로 인코딩합니다. MDN에 의하면 Canvas API는 바로 사용하기 까다로우니 쉽고 빠른 개발을 위해 라이브러리를 활용해 볼 것을 추천한다고 합니다. 하지만 구현 목표가 간단하고 공부도 해볼 겸 Canvas API를 직접 사용했습니다.

“Canvas API로 애니메이션을 구현”한다는 말은 난해하지만 “한 장면씩 만들어서 이어 붙이기”는 훨씬 직관적이고 쉬워 보입니다. 이렇게 어렵게 느껴지는 부분을 단순한 멘탈 모델로 형성하기 위해, 애니메이션이 프레임의 연속이라는 점에 착안하여 하나의 프레임만을 그리는 renderFrame() 함수를 먼저 설계했습니다.

renderFrame()은 캔버스 요소와 텍스트값, 그리고 위치, 방식, 정렬, 폰트, 색상 등의 옵션을 받아서 한 장면만을 캔버스에 그립니다.

renderFrame 함수의 구조

지금까지 Slack에 등록됐던 커스텀 이모티콘을 바탕으로 캔버스 프레임에 맞춰 텍스트를 그리는 방법(fit)을 다음의 네 가지로 분류했습니다.

  • width
  • height
  • both
  • both-stretch

width와 height는 각각 캔버스 너비와 높이에 맞춰 텍스트를 그리고, both는 텍스트의 너비와 높이가 캔버스를 넘지 않게 그립니다. both는 CSS의 object-fit: contain과 동작 방식이 같습니다.

이모티콘 크기가 작아서 최대한의 가독성을 위해 사용자가 무엇을 입력하든 캔버스에 가득 채워야 합니다. “ㅇ”과 “ㅏ”, 그리고 “오”와 “홓” 처럼 폰트의 각 glyph는 그 크기가 모두 다르기 때문에 캔버스 높이로 폰트 크기를 설정하면 원하는 결과가 나오지 않습니다. 그래서 이 텍스트가 화면에 그려진 실제 크기를 알아야 합니다.

텍스트의 너비는 Canvas API의 measureText() 메서드가 반환하는 TextMetricswidth 속성으로 바로 알 수 있습니다. 하지만 이것도 폰트에 디자인된 자간이 합쳐진 값이기 때문에 자간을 빼줘야 실제 너비가 됩니다. 그리고 actualBoundingBoxAscentactualBoundingBoxDescent 속성은 각각 폰트의 baseline으로부터 렌더링 된 폰트의 위와 아래까지의 거리이고 이 둘을 합한 값이 높이가 됩니다.

이렇게 계산된 텍스트의 실제 크기와 캔버스의 크기를 비교하여 각 fit 종류에 맞게 최종적으로 스케일링합니다.

renderFrame()은 캔버스에 텍스트를 그린 후에 그려진 위치와 크기를 반환하고 이 값은 다음 프레임을 그리는 데 사용됩니다. 예를 들어, 왼쪽으로 슬라이드 되는 애니메이션(slide())은 일정한 시간 간격으로 x축을 변경하면서 renderFrame()을 실행합니다.

왼쪽 슬라이드 애니메이션의 의사코드

마치며

everymoji를 통해 사이드 프로젝트에 대한 저의 인사이트를 보여드렸는데요, 재밌게 읽으셨길 바랍니다.

서비스를 오픈하고 사내 Slack 채널에 공유했을 때 반응이 정말 좋았고 지금까지 약 6,000여 개의 이모티콘이 만들어지면서 꾸준히 사랑받고 있습니다. 쉽게 사용할 수 있도록 고민한 디자인도 성공적인 것 같네요.

사내에서만 쓰기엔 아까워서 이 글을 통해 everymoji가 조금이나마 더 많은 분께 닿았으면 하는 바람도 있습니다.

코딩 그 자체도 재밌지만, 제가 확신하는 가치와 아름다움이 다른 사람들에게 고스란히 전해지고 인정받을 때 가장 자랑스럽고 뿌듯합니다. 이런 값진 경험을 할 수 있게 호응해 주고 사용해 주시는 모든 분께 감사드립니다.

저는 더 재밌는 이야기로 돌아오겠습니다.