시간 기반 CSS 애니메이션 기술
(yuanchuan.dev)- CSS 수학 함수 지원이 늘면서 keyframes 대신 시간 tick 변수
--t로 애니메이션 상태를 계산하는 접근이 다시 실용화됨 @property --t와86400000ms길이의 루트 애니메이션을 조합하면 밀리초 단위 시간이 증가하고, 이를rotate,translate같은 속성 계산에 연결할 수 있음step()을 쓰면 8fps, 24fps, 60fps처럼 프레임 레이트를 제한할 수 있고, 브라우저 최적화 덕분에 60fps보다 높은 갱신 빈도도 보통 문제되지 않음min(),mod(),sin(),pow(),round()로 시간 값을 제한·반복·진동·완화 처리해 커스텀 easing과 위치 이동을 keyframes 없이 표현함css-doodle은@t,@T,@TS같은 축약 표현을 제공해 수학식이 많은 데모를 더 짧게 작성하고 빠르게 실험할 수 있음
시간 tick을 CSS 변수로 만들기
- 과거 접근은 CSS에서 복잡한 수학 계산을 하기 어려워 적용 범위가 제한적이었음
- 현재 CSS는
mod(),round(), 삼각함수 등 필요한 수학 함수를 지원해 시간 기반 애니메이션을 다시 시도할 수 있음 - 데모 확인에는 Experimental feature flags 활성화가 필요할 수 있음
기본 동작 방식
- JavaScript처럼 CSS가 직접 타이머를 시작할 수는 없지만, CSS Houdini API로 커스텀 변수
--t를 정의해 밀리초 단위 시간을 추적할 수 있음 - 기본 예시는
@property --t를<integer>로 선언하고,@keyframes tick에서0부터86400000까지 변화시키며,:root에86400000ms linear infinite애니메이션을 적용함 --t는 1밀리초마다1씩 증가하며 1초에는1000이 됨counter()를 사용하면 현재--t값을 화면에 표시할 수 있음rotate: calc(var(--t) * .001turn)처럼--t기반 계산을 속성에 연결하면 시간이 흐르며 값이 바뀌고 애니메이션으로 이어짐
프레임 레이트 제어
- 부드러운 애니메이션에는 60fps 갱신 빈도로 충분함
- 브라우저 렌더링 최적화가 있어 갱신 빈도가 60fps보다 높아도 보통 문제가 되지 않음
- 필요하면
animation-timing-function에 step() 을 사용해 프레임 레이트를 직접 제한할 수 있음- 8fps:
step(calc(86400000/(1000/8))) - 24fps:
step(calc(86400000/(1000/24))) - 60fps:
step(calc(86400000/(1000/60)))
- 8fps:
증가하는 시간 값을 애니메이션 값으로 바꾸기
--t값은 한 방향으로 계속 증가함angle값은360deg를 넘어도 괜찮지만, 모든 CSS 속성이 값을 순환적으로 처리하지는 않음translate: calc(var(--t) * .001px)는 박스를 계속 오른쪽으로 이동시킴-
min()으로 멈추기
- 특정 offset에 도달하자마자 멈추게 하려면 min() 을 사용할 수 있음
translate: min(270px, calc(var(--t) * .5px))는 이동 값을 270px로 제한함- 지속 시간을 정확히 제어하려면
--t자체를 제한할 수 있음 translate: calc(min(3000, var(--t)) * (270px / 3000))- 이 계산은 3초 동안 270px 이동하게 만듦
-
mod()로 반복하기
- 박스가 오른쪽으로 이동한 뒤 처음부터 다시 시작하려면 mod() 를 사용함
translate: calc(mod(var(--t)/4, 270) * 1px)로 offset을 반복시킬 수 있음
-
sin()으로 왕복 이동 만들기
- 왕복 이동은 sin() 으로 표현할 수 있음
translate: calc(sin(mod(var(--t)/135, 270)) * 135px)처럼 시간 값을 삼각함수에 연결함
커스텀 easing 함수
- CSS 수학 함수와
--t변수로cubic-bezier()로는 달성하기 어려울 수 있는 커스텀 easing을 만들 수 있음 - 먼저
--t를0과1사이 값으로 제한함--t01: calc(min(1000, var(--t)) / 1000)는 1초 동안 0에서 1로 변화함
-
ease-out-cubic
ease-out-cubic은1 - pow(1 - t, 3)계산식으로 구성됨--ease-out-cubic값을 만든 뒤translate: calc(var(--ease-out-cubic) * 270px)에 연결함
-
ease-out-elastic
ease-out-elastic은pow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1계산식으로 구성됨- 같은 방식으로
--ease-out-elastic값을 만들고translate에 곱해 270px 이동에 적용함
css-doodle로 수식 줄이기
- 수식이 복잡해질수록
var()와calc()가 코드를 읽기 어렵게 만듦 - css-doodle 최신 버전은
--t를 표현하는 @t 함수를 추가했고, 인자 안에서 간단한 수학식을 직접 받을 수 있음 rotate표현은 다음처럼 줄어듦- 기존:
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) css-doodle:rotate: @t(/1000, %10, *5deg)
- 기존:
- keyframes를 작성하지 않아도 짧은 코드로 수학 기반 애니메이션을 만들 수 있음
- 매개변수를 바꾸면 새로운 결과를 빠르게 실험할 수 있음
-
@T와 @TS
@t외에 대문자 @T는 하루 시작부터의 time ticks를 나타냄- @TS는
@t(/1000)의 shorthand이며 시간을 초 단위로 추적함 - 시계 예시는 CodePen에서 볼 수 있음
- 초침:
rotate: @TS(*6, %360deg) - 분침:
rotate: @TS(/60, *6, %360deg) - 시침:
rotate: @TS(/60, /12, *6, %360deg)
-
round()로 초침 점프 만들기
- 초침을 점프하듯 움직이게 하려면 round() 를 사용할 수 있음
round()의 세 번째 매개변수는 반올림 간격을 지정함- 시계에서는 한 단계가
360 / 60 = 6deg이므로rotate: round(down, @TS(*6, %360deg), 6deg)를 사용함
데모와 적용 포인트
댓글과 토론
Hacker News 의견들
-
JS로 진행도를 제어하고 싶다면 음수 animation-delay 값을 쓰는 것도 추천함
예를 들어animation-delay: -1500ms는 애니메이션을 즉시 시작하되 1.5초 지점으로 건너뜀
이 값을 JS로 제어하면 CSS 애니메이션을 JS에서 사실상 스크러빙할 수 있어, 모든 애니메이션을 게임 엔진식 compute-update-render 틱 루프와 맞출 수 있음
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...- 그러면 iOS 스타일의 스크롤 오프셋 기반 애니메이션도 가능해지는 것 아닌가?
-
단순한 이징 함수나 한두 채널의 기본 키프레임보다 조금만 복잡해지면 이 접근의 한계에 금방 부딪힘
지난 몇 년간 Theatre.js를 써왔고 꽤 만족함
키프레임과 베지어 곡선을 타임라인에서 편집하는 스튜디오 UI와, 그 키프레임을 타임라인 기준으로 보간하는 런타임으로 나뉜 라이브러리임
여러 애니메이션을 맞춰 움직여야 한다면 써볼 만함
https://www.theatrejs.com/ -
매우 인상적이지만, 어쩐지 수십 년 전 Flash로 가능했던 것에 이제야 가까워진 느낌도 듦
Flash가 돌아오길 바라는 건 아니지만, CSS 애니메이션을 만들기 위한 더 사용자 친화적인 도구가 있었으면 함 -
CSS는 이제
mod(),round(), 삼각함수 같은 수학 함수 지원이 충분해졌음
CSS는 JavaScript처럼 타이머를 시작할 수는 없지만, 요즘은 CSS Houdini API로 밀리초 단위 시간을 추적하는 사용자 정의 변수를 정의할 수 있음
그런데 JavaScript가 있는데 왜 이 모든 게 필요할까?
그리기 계층은 그리기 기본 요소에 집중해야 하지 않나? 왜 더 많은 상위 계층 기능을 거기에 넣는 걸까?- CSS 애니메이션은 하드웨어 가속된다는 점을 잊으면 안 됨
애니메이션을 위해 JS에서 삼각함수를 돌린다면, 아마 매 밀리초마다 UI를 갱신하려고 DOM도 조작하고 있을 텐데 그 비용이 매우 큼 - CSS는 그리기 계층이 아님
그리기는 HTML로 구성되거나 JS로 갱신되는 DOM이 주도함
CSS는 DOM에 대해 무엇을 할지 페인팅 엔진에 매개변수를 넘기며 기본값을 덮어쓰는 식으로 얹혀 있을 뿐임
DOM 블록을 추가할 수도 있고, 복잡한 알고리즘으로 위치 지정 동작을 바꿀 수도 있으며, 마진조차 사실 그렇게 단순하지 않음
훌륭한 해법이라고 보진 않지만, DOM 렌더링 중 색상을 바꾸기 위해 HTML 코드를 갱신하지 않아도 되도록 하려다 시작된 것으로 보임
아마 HTML 코드 갱신이 전혀 즐겁지 않았기 때문일 텐데, 애초에 잘못된 문제를 풀었다고 봄
그때가 “웹마스터” 시대였다는 점도 있긴 함 - 떠오르는 장점 하나는 애플리케이션 로직과 표현의 분리임
fetch 로직 안에 로더 DOM 요소의 기하 진행 로직, 예를 들어 회전 각도 계산과 인라인 스타일 적용 같은 것이 섞이지 않으면 읽기 쉬움
요즘은 초콜릿과 땅콩버터를 따로 두는 방식이 인기 없다는 건 알지만, 이런 관례는 코드가 어디에 있을지, 어디에 넣어야 할지 추론하기 쉽게 경계를 그어줌 - JS는 기본적으로 단일 스레드라서, 물론 워커가 있긴 하지만, UI를 안정적인 프레임률로 유지하려면 다른 곳으로 작업을 넘길 수 있는 건 무엇이든 좋음
- CSS에 WebSocket이 들어갈 때까지 기다려 보자!
농담은 제쳐두고, CSS는 선언적이며 JS와 CSS로 풀 수 있는 문제가 겹치는 건 자연스러운 일이라고 봄
최근 CSS에 추가되는 새 기능들이 정말 기대되고, 해석되는 코드가 줄어드는 건 좋은 일임
- CSS 애니메이션은 하드웨어 가속된다는 점을 잊으면 안 됨
-
글에서 사용한 사용자 정의 CSS
@property는 훌륭하고, 브라우저 지원도 88% 임 [1]
다만 대체initial-value를 설정할 때 브라우저별 동작 차이를 조심해야 함
Chrome은 CSS 변수가 정의되지 않았거나 잘못된 값으로 설정된 경우initial-value를 사용함
Firefox는 변수가 정의되지 않았을 때만initial-value를 사용함
대부분의 프로젝트에서는 문제가 안 되겠지만, 최근 프로젝트에서는 브라우저 구현 차이를 맞추려고 Firefox에서 기본값을 JavaScript로 설정해야 했음
[1] https://caniuse.com/?search=%40property- Firefox에서 데모를 시험해보고 싶다면
about:config로 가서layout.css.properties-and-values.enabled를 켜면 됨 - 필요할 때 JavaScript에서 속성값을 설정하는 방법은 이렇음
:root{}에 정의된 속성값 가져오기:
getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");
속성값 설정하기:
document.documentElement.style.setProperty("--some-custom-css-var", "some value");
아직 CSS@property지원이 켜지지 않은 브라우저에서는 폴리필처럼 쓸 수 있음
- Firefox에서 데모를 시험해보고 싶다면
-
최신 Android Chrome에서는 흥미롭게도 Experimental feature flags 없이도
mod()이전 데모들은 동작하지만,mod()와sin()은 아무 일도 하지 않음 -
한동안 이걸 원했던 이유가 box-breathing 같은 걸 만들고 싶어서였음
들이쉬기-멈추기-내쉬기-멈추기 단계를 몸에 맞게, 원하는 효과에 맞게 모두 조정할 수 있게 하고 싶었음
이 페이지의 방식 같은 것이 없이는 JS 없이, 혹은 말도 안 되는 복잡도 없이 구현할 방법을 찾지 못했음
세상에 스타일시트라니! 이 페이지 데모들이 정말 창의적임- 그건 그냥 CSS의 애니메이션 곡선 아닌가?
-
글은 정말 잘 쓰였음
Chrome은 아직 CSSmod()지원을 정식 릴리스하지 않아서, Chrome 미리보기 릴리스를 쓰지 않으면 페이지의 예제 대부분이 애니메이션되지 않음
틱 제어가 필요 없고 모든 브라우저에서 부드러운 CSS 애니메이션만 원한다면 FLIP 기법이 유용함
https://medium.com/outsystems-experts/flip-your-60-fps-anima... -
페이지 하단의 애니메이션이 매우 인상적임
개별 요소라기보다 하나의 영상처럼 보임 -
https://RTCode.io는 새로고침 없이, 상태 초기화 없이 동작하는 실시간 웹 플레이그라운드라서 CSS 애니메이션이나 전환을 다루는 사람에게 큰 도움이 될 수 있음
데모는 https://RTCode.io에 있음
Pen을 가져오려면 URL의 호스트명을xcodepen.io로 바꾸면 됨- RTCode를 써본 사람 있나? 흥미로워 보이지만, 홈페이지를 보면 실시간 지원이 있는 CodePen 대체재인지, 아니면 프로덕션에도 쓸 수 있는지 조금 헷갈림