부드러운 웹 애니메이션을 위한 10가지 원칙
CSS로 60fps 애니메이션을 만드는 완벽 가이드

작년에 Gyroscope를 내놓은 뒤로 우리가 애니메이션에 쓰는 JavaScript 라이브러리에 대해 묻는 분이 많았어요. 공개할까도 생각해 봤지만, 사실 마법은 거기서 일어나지 않아요.
어떤 특별한 JavaScript 플러그인이 이 문제들을 마법처럼 풀어 준다고, 거기에 기대야 한다고 느끼게 하고 싶지 않아요. 우리는 대체로 최근에 좋아진 브라우저 성능과 GPU, CSS3 스펙을 활용하고 있을 뿐이에요.
훌륭한 애니메이션에 은탄환은 없어요. 오래 시험하고 다듬는 것 말고는요. 그래도 몇 년간 실험하고 브라우저 성능의 한계에 부딪히면서, 꽤 믿을 만하게 좋은 애니메이션으로 이어지는 디자인 & 코드 원칙들을 정리하게 됐어요. 이 방법들로 부드럽게 느껴지고, 요즘 데스크톱과 모바일 브라우저에서 잘 돌아가고, 무엇보다 관리하기 쉬운 페이지를 만들 수 있어요.

기술과 구현은 각자 조금씩 다르겠지만, 큰 원칙들은 거의 어떤 상황에서든 도움이 될 거예요.
애니메이션이란 무엇일까요?
애니메이션은 인터넷이 생기기 전부터 있었고, 잘 만드는 법은 평생을 배워도 좋을 주제예요. 다만 인터넷에서 만들 때는 나름의 제약과 어려움이 있어요.
부드러운 60fps를 내려면 한 프레임을 16ms 안에 그려야 해요! 그리 넉넉한 시간이 아니니, 매 프레임을 아주 효율적으로 그릴 방법을 찾아야 해요.
웹에서 애니메이션을 만드는 방법은 수십 가지예요. 예를 들어 필름스트립은 인터넷 이전부터 있던 방식으로, 조금씩 다르게 그린 프레임을 1초에 여러 번 바꿔 끼우면서 움직임의 착각을 만들어요.
Twitter도 최근 새 하트 애니메이션에 이 단순한 방식을 썼어요. 스프라이트에 담긴 프레임들을 넘기는 식으로요.

이 효과는 아주 작은 요소를 잔뜩 만들어 하나하나 움직이거나 SVG로 만들 수도 있었겠지만, 쓸데없이 복잡했을 테고 아마 이만큼 부드럽지도 않았을 거예요.

많은 경우에는 요소가 바뀔 때 자동으로 움직이도록 CSS transition 속성을 쓰게 돼요. 이 기법은 "tweening"이라고도 하는데, 두 값 사이를 오가며 이어 준다는 뜻이에요. 그 로직을 직접 만들지 않아도 쉽게 취소하거나 되돌릴 수 있다는 장점이 있어요. 인트로처럼 걸어 두고 잊어버리는 애니메이션이나 호버 같은 간단한 상호작용에 딱 맞아요.
더 읽을거리: CSS Transitions에 대해 알아야 할 모든 것

반대로 계속 돌아가는 배경 요소에는 키프레임 기반의 CSS animation 속성이 딱 맞을 수 있어요. 예를 들어 Gyroscope 로고의 링들은 계속 돌도록 잡혀 있어요. 기어 비율 같은 것도 CSS animation 문법이 잘 어울리는 경우예요.
자, 이제 애니메이션 성능을 크게 끌어올려 줄 요령들을 소개할게요…
#1
opacity와 transform 말고는 아무 속성도 바꾸지 마세요!
괜찮을 것 같아도 하지 마세요!
이 기본 원칙 하나만 지켜도 모바일에서까지 80%는 온 거예요. 아마 전에도 들어 봤을 거예요. 새로운 이야기는 아니지만 잘 지켜지지 않죠. "건강하게 먹고 운동하라"의 웹 버전 같아서, 좋은 조언인 건 알지만 대개 흘려듣게 돼요.
그렇게 생각하는 데 익숙해지면 꽤 단순한 이야기지만, 전통적인 CSS 속성을 움직이는 데 익숙했다면 큰 도약처럼 느껴질 수 있어요.
예를 들어 무언가를 작게 만들고 싶다면 너비를 바꾸는 대신 transform: scale()을 쓰면 돼요. 위치를 옮기고 싶다면 프레임마다 페이지 레이아웃 전체를 다시 만들어야 하는 margin이나 padding을 건드리는 대신, 간단하게 transform: translateX나 transform: translateY를 쓰면 돼요.
왜 이게 통할까요?
사람 눈에는 width나 margin 같은 속성을 바꾸는 게 별일 아니게 보이고, 더 단순하니까 오히려 나아 보일 수도 있어요. 그런데 컴퓨터가 해야 하는 일로 보면 둘은 하늘과 땅 차이이고, 한쪽은 훨씬, 훨씬 나빠요.
브라우저 팀들은 이 동작들을 최적화하는 데 정말 많은 공을 들였어요. transform은 효율적으로 처리하기가 아주 쉽고, 요소를 다시 그리지 않고 그래픽카드의 힘을 빌릴 수 있는 경우가 많아요.
페이지를 처음 불러올 때는 마음껏 해도 돼요. 모서리를 다 둥글리고, 이미지를 쓰고, 모든 것에 그림자를 넣고, 아주 겁이 없다면 동적 블러까지 걸어도 되죠. 한 번만 일어나는 일이라면 몇 밀리초 더 계산하는 건 문제가 안 돼요. 하지만 내용이 다 그려진 뒤에는 모든 걸 계속 다시 계산하게 두면 안 돼요.
더 읽을거리: translate로 요소 옮기기 (Paul Irish)
#2
보이는 곳에 숨기세요.
요소를 숨길 때는 opacity 0과 pointer-events: none을 함께 쓰세요
브라우저마다 조금씩 주의할 점이 있을 수 있지만, webkit을 비롯한 요즘 브라우저만 겨냥한다면 삶이 훨씬 편해질 거예요.
아주 예전에 jQuery의 animate()로 애니메이션을 다뤄야 했을 때는, 무언가를 서서히 나타내고 사라지게 하는 복잡함의 상당 부분이 display: none과 block을 딱 맞는 순간에 바꾸는 데서 왔어요. 너무 이르면 애니메이션이 끝나지 않고, 너무 늦으면 투명도 0인 보이지 않는 내용이 페이지를 덮고 있었죠. 애니메이션이 끝난 뒤 뒷정리를 하려고 모든 것에 콜백이 필요했고요.
CSS의 pointer-events 속성은(나온 지 꽤 오래됐지만 자주 쓰이지는 않아요) 요소가 클릭이나 상호작용에 반응하지 않게 만들어요. 마치 거기 없는 것처럼요. CSS로 쉽게 켜고 끌 수 있고, 애니메이션을 끊거나 렌더링과 표시 방식에 어떤 영향도 주지 않아요.
투명도 0과 함께 쓰면 사실상 display: none과 같은 효과가 나지만, 새로 렌더링을 일으키는 성능 부담은 없어요. 저는 무언가를 숨길 때 보통 opacity를 0으로 두고 pointer-events를 끈 다음, 그 요소는 알아서 잘 있겠거니 하고 잊어버려요.
이건 절대 위치로 놓인 요소에서 특히 잘 통해요. 그 요소가 페이지의 다른 어떤 것에도 전혀 영향을 주지 않는다고 확신할 수 있으니까요.
여유도 조금 더 생겨요. 타이밍이 완벽하지 않아도 되거든요. 어떤 요소가 보이는 시간보다 1초쯤 더 클릭되거나 다른 것을 덮고 있어도, 혹은 완전히 나타난 뒤에야 클릭되게 되어도 세상이 끝나지는 않아요.
#3
모든 걸 동시에 움직이지 마세요.
대신 안무를 짜세요.
애니메이션 하나만 놓고 보면 부드러울 수 있지만, 여러 개가 한꺼번에 돌아가면 아마 다 망가질 거예요. 뭐든 하나가 부드럽게 도는 기본 데모를 만드는 건 아주 쉽지만, 사이트 전체에서 그 성능을 지키는 건 자릿수가 다르게 어려워요. 그래서 순서를 제대로 짜는 게 중요해요.
모든 게 정확히 같은 순간에 시작하거나 돌아가지 않도록 타이밍을 펼쳐 두는 게 좋아요. 보통은 두세 개 정도가 동시에 움직여도 느려지지 않아요. 시작 시점이 조금씩 어긋나 있다면 더 그렇고요. 그보다 많아지면 끊김이 생길 위험이 커져요.
페이지에 정말 딱 하나만 있는 게 아니라면 안무라는 개념을 이해하는 게 중요해요. 춤에서 쓰는 말 같지만, 인터페이스를 움직일 때도 똑같이 중요해요. 요소들은 알맞은 방향에서 알맞은 순간에 들어와야 해요. 다 따로 움직이더라도, 잘 설계된 하나의 덩어리처럼 느껴져야 하고요.
Google의 머티리얼 디자인에 이 주제에 대한 흥미로운 제안들이 있어요. 그것만이 정답은 아니지만, 한번 생각해 보고 시험해 볼 만해요.

더 읽을거리: Google Material Design · Motion
#4
transition-delay를 조금씩 늘리면 움직임의 안무를 쉽게 짤 수 있어요.
애니메이션의 안무는 정말 중요하고, 제대로 느껴지게 만들려면 실험과 시험을 많이 해야 해요. 그래도 그 코드까지 복잡할 필요는 없어요.
저는 보통 부모 요소(대개 body)의 클래스 하나만 바꿔서 여러 transition을 한꺼번에 걸고, 각각에 서로 다른 transition-delay를 줘서 알맞은 때에 들어오게 해요. 코드로 보면 상태 변화 하나만 신경 쓰면 되고, JavaScript에 수십 개의 타이밍을 들고 있을 필요가 없어요.

여러 요소를 조금씩 어긋나게 등장시키는 건 안무를 짜는 쉽고 단순한 방법이에요. 보기에도 좋으면서 동시에 귀한 성능을 벌어 주니까 강력해요. 같은 순간에 벌어지는 일은 몇 개뿐이어야 한다는 걸 기억하세요. 하나하나가 부드럽게 느껴질 만큼은 벌려야 하지만, 전체가 너무 느려 보일 만큼 벌리면 안 돼요. 낱낱의 동작이 줄줄이 이어지는 게 아니라 하나의 흐름처럼 느껴질 만큼은 겹쳐야 해요.
코드 예시
요소를 어긋나게 만드는 간단한 방법이 몇 가지 있어요. 특히 목록이 길 때요. 항목이 10개 미만이거나 정적 페이지처럼 개수가 예측 가능하다면, 저는 보통 CSS에 값을 적어요. 가장 단순하고 관리하기도 쉬워요.

목록이 길거나 내용이 아주 유동적이라면, 항목을 하나씩 돌면서 타이밍을 동적으로 넣을 수 있어요.

보통 변수는 두 개예요. 기본 지연 시간, 그리고 항목 사이의 간격이요. 균형을 잡기가 까다롭지만, 딱 맞는 숫자를 찾으면 정말 완벽하게 느껴져요.
#5
전역 배수를 써서 슬로모션으로 디자인하세요
그리고 나중에 전체 속도를 올리세요.
애니메이션 디자인에서는 타이밍이 전부예요. 구현하는 데 20%가 들고, 나머지 80%는 모든 게 맞물리고 부드럽게 느껴지도록 알맞은 값 & 지속 시간을 찾는 데 들어가요.
특히 여러 요소의 안무를 짜면서 페이지에서 성능과 동시성을 짜내려 할 때, 전체를 슬로모션으로 보면 훨씬 쉬워져요.
JavaScript를 쓰든, 우리가 좋아하는 SASS 같은 CSS 전처리기를 쓰든, 계산을 조금 더 하고 변수로 만드는 건 그리 어렵지 않을 거예요.
여러 속도와 타이밍을 손쉽게 시험할 수 있게 해 두세요. 예를 들어 1/10 속도에서도 애니메이션이 끊긴다면 뭔가 근본적으로 잘못됐을 수 있어요. 50배로 늘렸을 때 부드럽다면, 이제 잘 돌아가는 가장 빠른 속도를 찾는 문제만 남은 거고요. 정상 속도에서는 5밀리초짜리 문제를 알아채기 어렵지만, 전체를 느리게 돌리면 아주 뚜렷하게 보여요.
특히 아주 복잡한 애니메이션이나 까다로운 성능 병목을 잡을 때, 슬로모션으로 볼 수 있다는 건 정말 쓸모 있어요.
핵심 생각은 이래요. 느리게 돌아가는 동안 완벽한 디테일을 잔뜩 채워 넣고, 그다음 전체 속도를 올려서 딱 맞게 느껴지게 하는 거예요. 아주 미묘하겠지만 사용자는 그 부드러움과 디테일을 알아차려요.
사실 이 기능은 OS X에도 들어 있어요. 최소화 버튼이나 앱 아이콘을 shift와 함께 클릭하면 슬로모션으로 움직이는 게 보여요. 한때 우리도 Gyroscope에 shift를 누르면 켜지는 슬로모션 기능을 넣어 두기도 했어요.
#6
UI를 영상으로 찍어서 다시 보면 값진 제3자의 눈을 얻을 수 있어요.
가끔은 관점을 바꾸면 더 또렷하게 보이는데, 영상이 그걸 하기에 아주 좋아요.
어떤 사람들은 After Effects로 영상을 만들고 그걸 사이트에 옮기려고 해요. 저는 반대로 가는 경우가 많아요. 사이트의 UI로 좋은 영상을 만들어 보려고 하죠.
무언가를 Vine*이나 영상으로 올릴 수 있다는 건 기준이 꽤 높다는 뜻이에요. 어느 날 제가 만든 게 마음에 들어서 친구들에게 보여 주려고 녹화를 했어요.
그런데 다시 보니 별로인 부분이 잔뜩 눈에 띄었어요. 큰 끊김이 하나 있었고 타이밍도 다 조금씩 어긋나 있었죠. 좀 민망해져서 보내는 대신, 할 일이 훨씬 많이 남았다는 걸 깨달았어요.
실시간으로 쓰는 동안에는 이런 걸 그냥 지나치기 쉬워요. 하지만 애니메이션을 영상으로, 몇 번이고 다시 보거나 슬로모션으로 보면 어떤 문제든 아주 뚜렷하게 드러나요.
카메라는 10파운드를 더해 준다고들 하죠. 어쩌면 10프레임도 더해 주는 모양이에요.
이제는 페이지를 슬로모션 영상으로 보고 어느 프레임이든 어색하면 고치는 게 제 작업의 중요한 부분이 됐어요. 브라우저가 느려서라고 탓하기는 쉽지만, 조금 더 다듬고 시험하면 그 문제들을 다 헤쳐 나갈 수 있어요.
영상에서 끊김이 잡혀도 부끄럽지 않고, 그 영상을 남에게 보여 줄 만하다 싶으면 그 페이지는 아마 내놓아도 될 때예요.
#7
네트워크 활동이 끊김을 만들 수 있어요.
큰 HTTP 요청은 미리 불러오거나 미뤄 두세요
이미지가 큰 범인이에요. 큰 이미지 몇 장(배경 같은 것)이든, 작은 것 수십 장(아바타 50개가 불러와진다고 생각해 보세요)이든, 그냥 내용이 많은 경우(푸터까지 이미지가 이어지는 긴 페이지)든요.
페이지가 처음 열릴 때는 온갖 것이 초기화되고 내려받아져요. 애널리틱스와 광고, 다른 서드파티 스크립트까지 있으면 더 나빠지고요. 로드 뒤에 모든 애니메이션을 몇백 밀리초만 미뤄도 성능이 놀랍게 좋아질 때가 있어요.
꼭 필요해지기 전까지 이걸 과하게 최적화하진 마세요. 다만 복잡한 페이지가 부드럽게 돌아가려면 내용의 지연과 타이밍을 아주 정밀하게 잡아야 할 수도 있어요. 대체로는 처음에 최소한의 데이터만 불러오고, 무거운 작업과 인트로 애니메이션이 끝난 뒤에 나머지 페이지를 마저 불러오는 게 좋아요.
데이터가 많은 페이지에서는 전부 불러오는 일 자체가 꽤 무거워요. 정적인 내용에서는 잘 돌아가던 애니메이션도, 동시에 진짜 데이터를 불러오기 시작하면 무너질 수 있어요. 될 것 같은데 안 되거나, 어떤 때는 부드럽고 어떤 때는 아니라면, 같은 시간에 다른 일을 하고 있지는 않은지 네트워크 활동을 확인해 보길 권해요.
#8
스크롤에 직접 묶지 마세요.
괜찮은 생각 같지만, 사실 별로예요.
스크롤 기반 애니메이션은 지난 몇 년 동안 꽤 인기를 얻었어요. 특히 패럴랙스나 다른 특수 효과가 들어간 것들이요. 그게 좋은 디자인인지는 논쟁의 여지가 있지만, 기술적으로 더 나은 구현과 더 나쁜 구현은 분명히 있어요.
이 부류에서 그럭저럭 성능이 나오는 방식은 특정 스크롤 위치에 닿는 것을 이벤트로 보고 한 번만 실행하는 거예요. 정말 잘 아는 게 아니라면 저는 이 부류를 피하라고 권하고 싶어요. 어긋나기가 너무 쉽고 관리하기도 정말 어렵거든요.
더 나쁜 건 기본 스크롤바를 쓰지 않고 직접 스크롤 동작을 만드는 것, 이른바 스크롤재킹이에요. 부디 하지 마세요.
이건 모바일에서 특히 쓸모 있는 규칙이지만, 가장 좋은 사용자 경험을 위해서도 지킬 만한 습관이에요.
스크롤이나 특별한 이벤트를 중심으로 한 경험을 꼭 만들고 싶다면, 디자인에 시간을 많이 쏟기 전에 성능이 나오는지 확인할 빠른 프로토타입부터 만들어 보길 권해요.
#9
모바일에서 일찍 & 자주 시험하세요.
대부분의 웹사이트는 컴퓨터에서 만들어지고, 대개 만든 그 기계에서 가장 자주 시험돼요. 그래서 모바일 경험 & 애니메이션 성능은 뒷전이 되기 쉬워요. canvas 같은 기술이나 일부 애니메이션 기법은 모바일에서 성능이 잘 안 나올 수도 있고요.
하지만 제대로 코딩하고 & 최적화하면(1번 규칙을 보세요) 모바일 경험이 컴퓨터보다 더 부드러울 수도 있어요. 모바일 최적화는 한때 아주 까다로운 주제였지만, 요즘 iPhone은 웬만한 노트북보다 빨라요! 앞의 요령들을 따라왔다면 별다른 손질 없이도 훌륭한 모바일 성능을 얻게 될 가능성이 커요.

거의 모든 사이트에서 모바일 사용은 크고 아주 중요한 부분이에요. 극단적으로 들릴 수 있지만, 한 주 내내 휴대폰으로만 보길 권해요. 모바일 버전을 억지로 써야 하는 게 벌처럼 느껴지면 안 되는데, 실제로는 그럴 때가 많아요.
큰 화면의 사이트만큼 잘 다듬어지고 편하게 느껴질 때까지 디자인 개선 & 성능 향상을 계속하세요.
한 주 동안 모바일 사이트만 쓰도록 스스로를 몰아붙이면, 아마 큰 화면보다 더 좋은 경험이 되도록 다듬게 될 거예요. 매일 쓰면서 짜증이 나더라도, 그 덕분에 사용자가 겪기 전에 문제가 고쳐진다면 그럴 만한 값어치가 있어요!
#10
여러 기기에서 자주 시험하세요
화면 크기와 밀도, 기기 종류 모두 큰 영향을 줄 수 있어요
모바일이냐 데스크톱이냐 말고도 성능을 크게 좌우하는 요소가 많아요. 화면이 "레티나"인지, 창의 전체 픽셀 수가 얼마인지, 하드웨어가 얼마나 오래됐는지 같은 것들이요.
Chrome과 Safari 둘 다 Webkit 기반이고 문법도 비슷하지만, 각자의 별난 구석이 있어요. Chrome은 업데이트마다 뭔가를 고치고 새 버그를 들여오기도 하니 늘 긴장하고 있어야 해요.
물론 가장 낮은 기준에만 맞춰 만들고 싶지는 않을 테니, 향상된 효과를 점진적으로 더하거나 빼는 똑똑한 방법을 찾으면 정말 쓸모 있어요.
저는 작은 MacBook Air와 커다란 iMac을 자주 오가는데, 그럴 때마다 고칠 작은 문제와 개선점이 드러나요. 특히 애니메이션 성능에서 그렇고, 전체 디자인과 정보 밀도, 가독성 같은 면에서도 그래요.
미디어 쿼리는 이런 서로 다른 조건을 다루는 데 아주 강력한 도구예요. 높이나 너비에 따라 다르게 꾸미는 게 흔한 쓰임새지만, 픽셀 밀도나 다른 속성으로 겨냥하는 데도 쓸 수 있어요. 모바일은 성능 특성이 컴퓨터와 아주 다를 수 있으니 OS와 기기 종류를 알아내는 것도 도움이 돼요.

다음 프로젝트에서 이 방법들이 도움이 되면 좋겠어요. 행운을 빌어요!


