Javascript Animation Performance

JavaScript 애니메이션 성능을 높이는 방법

애니메이션이 많은 화면에서는 성능과 관련된 문제가 일어나기 쉽다. 특히, 하드웨어 성능이 PC에 비해 열악한 모바일 환경에서는 성능이 문제가 되는 경우가 더욱 많다.

브라우저 렌더링 과정

웹 브라우저의 애니메이션 성능을 이야기하기 전에 이해해야 할 것이 브라우저의 렌더링 과정이다. 애니메이션도 브라우저 렌더링 과정의 한 형태이기 때문에 브라우저 렌더링 과정을 이해하면 성능에 미치는 부분을 찾을 수 있다.

HTML 렌더링 순서

  1. 브라우저가 HTMl을 파싱한다.
  2. DOM 트리를 구성한다.
  3. DOM 트리에서 화면에 표시되는 엘리멘트를 렌더 객체(RenderObject) 트리로 구성한다. 렌더 객체 트리는 DOM 트리에서 화면에 보이는 요소를 노드를 구성한다. 가령 display:none 속성인 요소나 HEAD, SCRIPT와 같이 화명에 보이지 않는 노드는 렌더 객체에서 제외된다.
  4. 렌더 객체 트리에서 별도의 영역으로 구성할 수 있는 노드를 렌더 레이어(RenderLayer) 트리로 구성한다. 렌더 레이어 트리는 렌더 객체 트리에서 transform과 같은 특수 속성이 적용된 노드로 구성된다.
  5. 브라우저는 구성된 정보를 바탕으로 비동기식으로 화명을 표현한다.

크롬 개발자 도구에 있는 Timeline 탭에서 실제 렌더링이 어떻게 이뤄지는지 확인 할 수 있다. Timeline 탭에서 HTML 을 불러오면 다음과 같이 렌더링 과정이 나타난다.

  • ParseHTML : HTML을 파싱할 때 발생한다.
  • Recalculate Sytyle : 요소에 스타일이 적용될 때 발생한다.
  • Layout : 적용된 스타일에 의해 위치나 크기가 변경되어 계산이 필요할 때 발생한다.
  • Paint : 요소를 화면에 다시 그려야 할 때 발생한다.
  • Composite Layer : 변경되지 않은 각 렌더 레이어를 합성할 때 발생한다.

브라우저는 문서를 화면에 표시하려고 위와 같은 다양한 과정을 수행한다. 로컬환경에서의 Timeline 탭에서는 HTML 파싱에 가장 많은 시간이 걸렸지만 실제 애니메이션은 HTML 파싱이 완료된 후 사용자의 동작에 의해 실행되기 때문에 애니메이션 성능에 영향을 미치는 요소는 다르다. 애니메이션이 동작할 때는 레이아웃 계산이나 연속적인 페인트(Paint) 작업으로 성능 문제가 발생한다.

애니메이션 성능을 높이는 방법

크롬 개발자 도구에서 알 수 있듯이 렌더링 과정 중에 DIV 요소에 적용된 스타일의 속성이 무엇이냐에 따라 레이아웃 작업이 발생하기도 하고, 레이어 병합(composite layers)작업이 이뤄지기도 한다.

레이어 구성하기

레이어는 얇은 투명막과 같이 여러 장을 겹쳐서 화면을 구성할 수 있는 요소로, 여기서는 렌더 객체를 지칭한다. 브라우저가 레이어를 구성하는 목적은 변경될 요소가 많은 영역을 별도로 관리해 해당 영역이 변경됐을 때 해당 부분만 반영하라는 것이다.

다음 그림은 한 화면이 레이어로 구성된 것을 표현한 그림이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- [I] 레이어 구성 구조
03. 앞면레이어
02. 마스크레이어
01. 영상레이어
00. 배경레이어
-->

<div> <!-- 00. 배경레이어 -->
<div> <!-- 01. 영상레이어 -->
<div> <!-- 02. 마스크레이어 -->
<div><!-- 03. 앞면레이어 --></div>
</div>
</div>
</div>

하나의 화면이 4개의 레이어로 구성돼 있고, 실제 화면에 표시될 때에는 4개의 레이어가 합쳐져 화면에 보이게 된다. 휴대폰 화면 속 영상이 변경될 때 실제 변경되는 부분은 전체 화면이 아니라 휴대폰 화면 속의 영상만이다. 배경과 휴대폰 모양의 사진은 기존 레이어(배경 레이어, 휴대폰 모양 사진 레이어)를 그대로 사용할 수 있다. 즉, 변경된 부분만 바꾸고 나머지 부분을 합쳐 빠르게 화면을 수성할 수 있다. 이를 레이어 병합이라고 한다. 레이어 병합을 사용하면 페인트나 레이아웃 작업이 발생하지 않기 때문에 변경된 화면을 빠르게 구성성할 수 있다.

이러한 레이어 구성은 브라우저가 판단하며, 그 규칙은 다음과 같다.

  • 3D(translate3d, preserve-3d 등) 나 perspective transform을 사용하는 경우
  • <video> 태그나 <canvas> 태그를 사용하는 경우
  • 플래시나 ActiveX 컨트롤을 사용하는 경우
  • CSS 애니메이션, CSS 필터를 사용하는 경우
  • 자식 요소가 레이어로 구성돼 있을 경우

z-index가 낮은 형제 요소가 레이어로 구성돼 있을 경우 대상 요소도 레이어로 구성된다.
애니메이션은 대상 요소의 스타일을 주기적으로 변경해서 이동한다. 스타일 변경 때문에 페인트나 레이아웃 작업이 연속적으로 발생해 애니메이션의 성능을 저하시키는 경우가 있다. 이때 레이어 병합을 이용해 페인트와 레이아웃 작업이 일어나지 않도록 유도하면 애니메이션의 성능을 높일 수 있다.

GPU 가속 사용하기

GPU 가속은 앞에서 설명한 레이어 병합 방식으로만 동작한다. 하지만 브라우저의 레이어 병합 방식과 달리 각 레이어를 GPU 메모리에 비트맵 형태의 텍스처(texture)로 저장해 실제 레이어를 병합할 때는 GPU의 메모리에서 실행 한다. 그래서 굉장히 빠르다.

다음 스타일이 적용되면 브라우저는 해당 요소 영역(layer)을 GPU 메모리에 텍스처로 저장한다.

1
2
3
4
-webkit-transform : translate3d
-webkit-transform : translateZ
-webkit-transform : rotate3d
-webkit-transform-style : preserved-3d

그러나 GPU는 하드웨어에 따라 성능이 달라지기 때문에 GPU 가속을 사용할 때는 주의를 기율여랴 한다. 무분별한 GPU 사용은 오히려 브라우저를 느리게 한다.요소에 GPU 사용 속성이 부여되는 순간 브라우저가 대상 영역을 GPU 메모리에 로딩하기 때문에 콘텐츠가 클 경우에는 화면이 깜빡이는 현상이 발생할 수 있다.

요소에 GPU 사용 속성이 부여되면 레이어로 분리되며 레이어는 변경되는 내용이 없는 한 다시 요소의 내용을 GPU 메모리에 올리지 않는다. GPU 사용 속성을 사용한 요소의 내용이 변경되면 GPU 메모리가 갱신되기 때문에 요소의 내용을 미리 변경하고 그 이후에 GPU 사용 속성을 부여한다.

성능이 안 좋은 휴대폰에서 하드웨어 가속을 사용하는 것은 오히려 성능 저하의 주범이 된다. 경험상 iOS 3.x 이상과 안드로이드 4.1 이상을 지원하는 기기에서 GPU 가속을 적용했을 때 더 성능이 좋았다.

애니메이션 동작 시 레이아웃을 변경하는 스타일이나 메서드 사용하지 않기

브라우저 렌더링 과정 중 가장 느린 작업 가운데 하나가 레이아웃 작업이다. 이작업은 요소의 위치나 크기를 계산하는 작업이라서 비용이 높고, 자주 하면 성능에 치명적이다. 특히, 주기적으로 요소의 스타일을 변경하는 애니메이션에서 너비나 높이와 같이 대상의 크기를 변경하는 레이아웃 작업이 발생하면 부드러운 애니메이션을 기대하기 어렵다.

다음은 레이아웃 작업을 유발하는 스타일이나 메서드를 정리한 표다.

대상 메서드 및 속성
요소 clientHeight, clientLeft, clientTop, clientWidth, focus(),
getBoundingClientRect(), getClientRects(), innerText, offsetHeight,
offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(),
scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(),
scrollLeft, scrollTop, scrollWidth
위치 left, top, position, float
박스, 테두리 height, width, padding, margin, display,border-width,border
범위 getBoundingClientRect(),getClientRects()
window 객체 getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY,
webkitConvertPointFromeNodeToPage(), webkitConvertPointFromeNode()

따라서 되도록 애니메이션을 동작할 때 위의 표에 나열된 메서드와 속성은 호출하거나 변경하지 않는 편이 좋다. 혹시 변경이 필요하다면 애니메이션을 동작할 때가 아니라 동작 전후에 미리 작업을 하거나 레이아웃 작업이 발생하지 않는 대체 속성을 사용하는 것이 좋다. 다음 속성을 대체하면 레이아웃 작업 대신 레이어 병합이 발생한다.

width, height -> transform:scale
left, top -> transform:translateX, translateY 또는 translate(x,y), translate3d(x,y,0)

공유하기