css, js 애니메이션에 대해 공부하다가 알게 된 사실을 정리해 본다.
브라우저가 콘텐츠를 화면에 나타내기 까지의 과정은 paint까지만 알고 있었는데, (이전포스트)
사실 composite라는 과정이 더 있었다.
이 과정에서 스타일 지정 방식에 따라 화면에 나타낼 요소들이 레이어로 분류되어 계층이 결정되고, 애니메이션을 처리하기 위해 GPU를 이용한다는 사실을 알게 되었다.
또, requestAnimationFrame()의 역활에 대한 정의가 모호했는데 이번 기회에 구체적으로 알게 되었다.
1. Recalculate Style
요소에 적용할 스타일을 계산.
2. Layout
요소의 레이아웃을 생성. 요소들은 벡터 박스로만 표현된다.
3. Paint
생성된 모든 벡터 박스들을 픽셀로 변환하고.(rasterize: 래스터화) 레이어에 올린다.
4. Composite Layers
생성한 레이어 계층을 합성하고 화면에 나타낸다.(프레임을 그린다)
모든 요소가 고유한 위치(복합 계층)를 갖는 완전한 웹 페이지로 보여진다.
Composite Layers 생성:
CPU가 애니메이션을 처리하기 위해 GPU와 통신하는 단계
각각의 layer를 GPU 메모리에 bitmap 형태의 texture로 저장하고 composition layer 작업시 GPU의 메모리에서 관련 작업 진행
GPU 사용시 주의
- 메모리를 사용하기 때문에 과용은 좋지 않다.
- 저사양의 폰에서 하드웨어의 가속은 오히려 성능 저하 유발
앞에서 언급했듯이 Layout 단계에서 style 속성이 무엇이냐에 따라 별도의 layer가 지정되거나 layout계산이 되기도 한다.
나뉜 layer들은 layout의 계산이나 Paint없이 병합을 통해서 새로운 화면을 구상할 수 있다.
이미지 출처: 웹 성능 최적화에 필요한 브라우저의 모든 것
<video>
혹은 <canvas>
태그 사용부드러운 애니메이션 동작을 위해 가능한 동작 전, 후에 미리 작업하거나 layout을 유발하지 않는 스타일을 사용하는 것이 좋다.(위 참고)
transform(translate): 컴포지터 쓰레드에서 Composite layer 작업만 수행.
-> 해당 요소가 새 레이어로 생성됨(UI 요소의 애니메이션과 아닌 일부를 나눠, 다시 렌더링 하지 않기 위함)
-> GPU는 이 렌더링 객체 트리를 메모리에 유지하고, 다시 렌더링 하지 않는 레이어를 위에 얹을 수 있다.
#animate {
animation: playAni 4s;
}
@keyframes playAni{
from{
transform: translate(0,0);
}
to{
transform: translate(350px,350px);
}
}
GPU에서 애니메이션을 처리하므로 메인 스레드를 자유롭게 유지할 수 있다.
웹 페이지의 성능을 향상 시키고, 메인 스레드는 렌더링 레이아웃에 집중할 수 있게 된다
top/left: 메인 스레드에서 Layout, Painting, Recalculating Style을 계속 수행
-> 동일한 레이어를 반복해서 렌더링
-> repaint, composite 작업이 다시 발생
#animate {
animation: playAni 4s;
}
@keyframes playAni{
from{
left: 0;
top: 0;
}
to{
left: 350px;
top: 350px;
}
}
레이어 분리가 되지 않고 reflow가 반복되어 웹 페이지 성능 최적화에 좋지 않다.
function playAni() {
let elem = document.getElementById("animate");
let pos = 0;
let id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
마찬가지로 reflow가 반복되어 웹 페이지 성능 최적화에 좋지 않다.(해결책 - 밑 참고)
CSS 애니메이션
JS 애니메이션
CSS로는 마우스 올렸을 때 or 메뉴 버튼 전환 등 간단한 애니메이션 구현을 (주로 transform,opacity 속성 사용),
JS는 CSS로 처리하기에는 훨씬 복잡하고 무거운 애니메이션을 세밀하게 다룰 수 있다.
바닐라 js는 reflow가 반복되기 때문에 부드러운 fps 위해서 RAF(RequestAnimationFrame)을 이용할 수 있다.
또, 외부 라이브러리인 velocity.js, GSAP를 이용하여 편리하게 이용 가능하다.
요소에 애니메이션을 적용하면 브라우저는 모든 프레임 사이에서 리플로우, 리페인트를 적용해야 한다.
대부분의 디스플레이 장치는 화면을 초당 60번 새로 고친다(60fps). 요소의 움직임이 모든 프레임에 반영되어야 사람이 볼 때 부드럽게 느껴진다.
frame: 초당 몇장의 정지 화면을 이어 붙여서 그 화면이 이루어지는지에 대한 낱장 단위, 초당 프레임은 Fps(Frame per second)라고 함.
화면 주사율에 맞추어 렌더링 작업이 이루어져도 이 작업은 메인 스레드에서 실행되기 때문에 애플리케이션이 JavaScript를 실행하는 동안 렌더링이 막힐 수 있다.
requestAnimationFrame(): 콜백함수가 애니메이션 프레임에 맞춰 js 작업을 작은 덩어리로 나눠 애니메이션 타임라인 실행함
(메인 스레드를 blocking하지 않기 위해 웹 워커에서 js를 실행 할 수도 있다.)
https://blog.logrocket.com/eliminate-content-repaints-with-the-new-layers-panel-in-chrome-e2c306d4d752/
https://d2.naver.com/helloworld/5237120
https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count
https://medium.com/chegg/performance-monitoring-in-css-animations-f11a21d0054f