웹으로 빚은 다이나믹 아일랜드
🏝
그럼요. 아래 검정색 타원을 클릭해보세요. 제가 현재 듣고 있거나 가장 최근에 들은 30개의 음악 중 하나가 무작위로 나타날거예요.
위의 검정색 타원을 클릭해보세요.
리버스엔지니어링한 Apple Music API가 변경되었고, 때문에 API를 더이상 업데이트하지 않을 예정입니다.
유능한 예술가는 모방하고 위대한 예술가는 훔친다 — 그리고 이번에는 Vercel의 개발자 경험 담당 VP Lee Robinson의 아이디어 하나를 베껴보려 한다. Next.js의 각종 신기술을 활용하는 실험적이지만 간결하고 아름다운 포트폴리오로 유명한 leerob.io에는 한 가지 재미있는 기능이 있다. 자신이 현재 듣고 있는 음악이 웹에 같이 나타나는 기능이다.
누구보다 음악적 취향이 뚜렷한 나였기에 언젠가는 이를 꼭 내 웹사이트에 구현하고 싶었다. 다만 단순하게 재현하는 수준이 아니라 무언가 기술적인 도전을 해보고 싶었다. 당시 여러 음악 서비스를 돌아가며 사용해보고 있었기에 굳이 개발을 서두르지 않은 이유도 있었다. 그런 이유로 개발을 이리저리 미루다 2022년에 이르렀다. 그러던 중 Apple이 최근 신박한 새로운 기능을 발표했다. 바로 다이나믹 아일랜드(Dynamic Island)라는 기능이다.
화면 상단 펀치홀이 자유자재로 크기를 바꾸며 다양한 부가 정보를 표시한다.
내가 바라고 있던 기술적인 도전이었다! 이를 웹에서 정확하게 똑같이 구현해보고 싶다는 결론에 이르렀다. 몇 가지 안드로이드 전용 복제품을 인터넷에서 확인하기도 했는데, 전부 애니메이션 곡선이 부자연스러워 이런 디테일을 공부해보고 싶었다.
웹 상에서 내가 현재 듣고 있는 음악을 보여주는 다이나믹 아일랜드 🏝 를 구현해보자.
이 프로젝트의 연구 기록도 공개되어 있다.
🛠 기술 정하기
우선 프레임워크로는 가장 익숙한 Next.js와 Tailwind를 골랐다. 문제는 애니메이션이었다. 간단한 CSS 애니메이션은 다루어 보았지만 ease-in-ease-out 이상의 복잡한 애니메이션은 다뤄보지 못했다. Framer Motion이라는 라이브러리를 알게 되어 이를 사용하기로 했다.
🧑🏻🏫 애니메이션의 물리
우선 Apple의 애니메이션과 다른 모방작의 애니메이션이 왜 달라보이는지부터 이해해야 한다. 애니메이션에도 다양한 종류가 있지만, 크게 2가지로 나눌 수 있다. (최소한 Apple 플랫폼에서는 이렇게 2가지로 나누어 지원한다.) 아주 단순하게 이해하자면 다음과 같다.
Parametric Curve. 시작점과 종착점이 있을 때, 그 사이 조작점(Control Point)을 두고 그 조작점 사이를 수학 공식을 이용 해 보간(interpolate)한다. 보간에 사용되는 공식의 종류에 따라 Linear Curve, Polynomial Curve, Spline Curve 등으로 나뉜다. 흔히 사용되는 Bezier Curve도 여기에 해당된다.
Spring Curve. 고전 물리학의 탄성 방정식(Hooke's law와 그에 기반한 수많은 방정식)을 이용해 경직도(Stiffness)와 제동 계수(Dampening)를 통한 물리적인 궤도를 계산한다. 더 알아보기: Maxime Heckel
애니메이션 곡선에 대해서 더 깊게 이야기하는 것은 이 글의 초점에서 벗어나니 더 자세하게 설명하지는 않겠지만,
대부분의 다이나믹 아일랜드 재현작들이 위의 Parametric Curve를 이용해 애니메이션을 제작하는 반면 (CSS에 내장되어 제공되니 가장 쉽기도 하다)
Apple의 경우 현실의 애니메이션과 비슷하게 구현하기 위함인지 Spring Motion을 주로 사용한다.
이번에 사용한 Framer Motion에서도 useSpring()
이라는 React Hook을 통해 이런 물리적인 움직임을 제어할 수 있다.
import { useSpring } from 'framer-motion'
useSpring(x, { stiffness: 1000, damping: 10 })
🛥 다이나믹 아일랜드를 향해
우선 Apple의 문서를 깊이 읽어보며 이런 저런 특징들을 공부했다. 다이나믹 아일랜드는 크기에 따라 다음의 형태를 지닌다.
더불어서 인터넷 어딘가에서 다음 사진도 확인할 수 있었다. Apple 공식 문서에서는 단순하게 Expanded라고 두루뭉실하게 표현하는 반면 이 사진에는 여러 사이즈가 동시에 나타난다.
이를 반영해서 다음처럼 타입 정의를 해보았다.
export type DynamicIslandSize =
| 'compact'
| 'minimalLeading'
| 'minimalTrailing'
| 'default'
| 'long'
| 'large'
| 'ultra'
그런 다음 하룻밤을 갈아넣어 (2022년 10월 16일) Framer Motion을 이용해 자연스럽게 크기 전환을 하는 방법을 알아냈다.
다음의 코드로 동작한다.
특히 stiffness
와 damping
값을 가지고 많은 실험을 했다.
알아낸 값은 const stiffness = 400
그리고 const damping = 30
.
<motion.div
id={props.id}
initial={{
opacity: props.size === props.before ? 1 : 0,
scale: props.size === props.before ? 1 : 0.9,
}}
animate={{
opacity: props.size === props.before ? 0 : 1,
scale: props.size === props.before ? 0.9 : 1,
transition: { type: 'spring', stiffness: stiffness, damping: damping },
}}
exit={{ opacity: 0, filter: 'blur(10px)', scale: 0 }}
style={{ willChange }}
className={props.className}
>
2022년 10월 16일 완성한 모습
📞 전화 구현
다른 API를 붙이기 전에 처음 한 것은 전화 컴포넌트를 구현하는 일이었다. 큰 이유가 있었던 것은 아니고 애니메이션과 익숙해지기 위해서 구현해보았다. 실제로 완성된 제품을 보니 실제 Apple의 제품과 아주 닮아 마음에 들었다. 2022년 10월 20일에 완성되었다.
↑ 클릭해보세요 ↑
🍎 Apple Music API
그 다음 필요했던 것은 Apple Music API와 연동하는 일이었다. 2021년 초에 Spotify를 이용해서 기술 시연을 완성한 적이 있다. Spotify에는 Now Playing API가 공식적으로 존재한다. 비슷하게 Apple Music도 Now Playing API가 있을 것이라고 생각했다.
Apple Music API 1.1이 언제 출시된 것인지 모르겠지만 비교적 최근에 Get Recently Played Tracks라는 이름으로 비슷한 API가 공개되었다.
참고로 2년 전만 해도 API가 존재하지 않았다.
이제 필요한 것은 OAuth 2.0에 필요한 각종 토큰들을 발급 및 저장하는 일이다. Spotify의 경우 OAuth 2.0 표준을 거의 그대로 따라가다시피 했는데 Apple은 몇 가지 정보가 더 필요했다. 특히 이 Now Playing 같은 경우는 단순하게 Apple과의 인증 뿐만 아니라 사용자의 정보에 접근하는 것이다 보니 사용자의 동의 여부에 따른 권한 처리도 별도로 진행되었다. 그리고 이 모든 것이 문서화가 제대로 되어있지 않아 꽤 골치 아팠다. 우선 필요했던 것은 다음과 같다.
비행기 | Apple에서의 동일한 개념 | 설명 |
---|---|---|
항공기 사업을 위한 비용 처리 | Apple Developer 유료 계정 | $99 |
조종사 자격증 | Apple Developer에서 발급 받는 Apple Music Key | Apple Music 관련 서비스에 내가 요청을 날릴 법적 권한이 있음을 확인한다. |
항공기 운항승인 신청서 | Apple 서버에 요청해서 발급 받는 Apple Music Main Token | Apple Music 관련 서비스에 내가 요청을 날릴 때 쓸 통행증을 발급 받는다. |
탑승자가 구매하는 비행기 티켓 | 사용자가 인증 플로우를 거쳐 발급 받는 Apple Music User Token |