Composite Layer(feat. css, js 애니메이션)

Bitnara Lee·2022년 3월 9일
3

css, js 애니메이션에 대해 공부하다가 알게 된 사실을 정리해 본다.

브라우저가 콘텐츠를 화면에 나타내기 까지의 과정은 paint까지만 알고 있었는데, (이전포스트)
사실 composite라는 과정이 더 있었다.
이 과정에서 스타일 지정 방식에 따라 화면에 나타낼 요소들이 레이어로 분류되어 계층이 결정되고, 애니메이션을 처리하기 위해 GPU를 이용한다는 사실을 알게 되었다.

또, requestAnimationFrame()의 역활에 대한 정의가 모호했는데 이번 기회에 구체적으로 알게 되었다.

✨ Reflow, Repaint가 일어나면

1. Recalculate Style
요소에 적용할 스타일을 계산.
2. Layout
요소의 레이아웃을 생성. 요소들은 벡터 박스로만 표현된다.
3. Paint
생성된 모든 벡터 박스들을 픽셀로 변환하고.(rasterize: 래스터화) 레이어에 올린다.
4. Composite Layers
생성한 레이어 계층을 합성하고 화면에 나타낸다.(프레임을 그린다)
모든 요소가 고유한 위치(복합 계층)를 갖는 완전한 웹 페이지로 보여진다.

⭐️ Composite Layers

Composite Layers 생성:
CPU가 애니메이션을 처리하기 위해 GPU와 통신하는 단계
각각의 layer를 GPU 메모리에 bitmap 형태의 texture로 저장하고 composition layer 작업시 GPU의 메모리에서 관련 작업 진행

GPU 사용시 주의

  • 메모리를 사용하기 때문에 과용은 좋지 않다.
  • 저사양의 폰에서 하드웨어의 가속은 오히려 성능 저하 유발

⭐️ 브라우저가 별도의 Layer를 구성할 때

앞에서 언급했듯이 Layout 단계에서 style 속성이 무엇이냐에 따라 별도의 layer가 지정되거나 layout계산이 되기도 한다.
나뉜 layer들은 layout의 계산이나 Paint없이 병합을 통해서 새로운 화면을 구상할 수 있다.

이미지 출처: 웹 성능 최적화에 필요한 브라우저의 모든 것

  • <video> 혹은 <canvas> 태그 사용
  • 3D, 2D transforms, opacity 적용(opacity는 1보다 작아야)
  • animation 이나 transition 사용
  • will-change로 opacity, transform, top, left, bottom, right등이 정의된 경우
  • iFrame일 경우
  • flash와 같은 일부 플러그인
  • backface-visibility 가 hidden일 경우

⭐️ Layout을 유발하는 스타일/메소드

부드러운 애니메이션 동작을 위해 가능한 동작 전, 후에 미리 작업하거나 layout을 유발하지 않는 스타일을 사용하는 것이 좋다.(위 참고)

  • Element : clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth
  • Position : left, top, position, float
  • BOX, Border : height, width, padding, margin, display, border-width, border
  • Range : getBoundingClientRect(), getClientRects()
  • Window : getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY, webkitConvertPointFromNodeToPage(), webkitConvertPointFromPageToNode()

🔸 ex) 움직이는 사각형 애니메이션 구현 시 (좌측 상단에서 우측 하단으로)

애니메이션 페이지

< CSS 애니메이션 >

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가 반복되어 웹 페이지 성능 최적화에 좋지 않다.

< JS 애니메이션 >

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 애니메이션 vs JS 애니메이션

CSS 애니메이션

  • 미디어 쿼리로 애니메이션을 적용하여 반응형 애니메이션 구현 가능
  • 외부 라이브러리가 필요하지 않다.
  • CSS자체가 선언형이기 때문에 어떤요소가 애니메이션을 가져아한다는 직관적인 표현 가능
  • 메인 쓰레드가 아닌 별도의 컴포지터 쓰레드(Compositor Thread)에서 그려지기 때문에 메인 쓰레드에서 작업하는 JS보다 효율적이다.
  • 모든 동작을 CSS에서 관리
  • 구 버전의 브라우저에서는 지원을 하지 않는 경우가 있다.(특히, IE) -> 크로스 브라우징 이슈

JS 애니메이션

  • 크로스 브라우징 이슈가 없다. 렌더링 엔진의 경우 브라우저 마다 달라 구현이 안되는 경우들이 있는데 JS 애니메이션은 호환성에 구애받지 않는다.
  • GPU를 통한 하드웨어 가속을 제어할 수 있다. CSS 애니메이션의 경우 특정 속성에 의한 GPU가속이 됨으로서 저사양의 컴퓨팅인 경우에 성능 하락을 발생시킬 수 있으나 이를 막을수 있다.
  • 요소의 스타일이 변하는 순간마다 제어가 가능하기 때문에 애니메이션의 세밀한 구성이 가능해진다.
  • JavaScript에서는 css, 동작을 모두 관리해야 한다.

CSS로는 마우스 올렸을 때 or 메뉴 버튼 전환 등 간단한 애니메이션 구현을 (주로 transform,opacity 속성 사용),
JS는 CSS로 처리하기에는 훨씬 복잡하고 무거운 애니메이션을 세밀하게 다룰 수 있다.
바닐라 js는 reflow가 반복되기 때문에 부드러운 fps 위해서 RAF(RequestAnimationFrame)을 이용할 수 있다.
또, 외부 라이브러리인 velocity.js, GSAP를 이용하여 편리하게 이용 가능하다.

✔️ requestAnimationFrame()

요소에 애니메이션을 적용하면 브라우저는 모든 프레임 사이에서 리플로우, 리페인트를 적용해야 한다.
대부분의 디스플레이 장치는 화면을 초당 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

profile
Creative Developer

0개의 댓글