requestAnimationFrame

송현섭 ·2024년 9월 7일
0

개별공부

목록 보기
43/44

RAF (RequestAnimationFrame)


  • 브라우저의 렌더링 주기에 맞춰 부드러운 애니메이션 효과를 제공할 수 있도록 하는 자바스크립트 내장 api

  • raf는 현재 해상도에 맞는 모니터의 주사율 만큼 (일반적으로 60fps) 이벤트를 발생시킴

  • raf 를 활용하여 reflow, repaint 최적화를 통해 성능향상 가능

  • 백그라운드 탭으로 이동 시, 실행을 중단하기 때문에 효율적인 성능 개선에 유리함

  • raf의 콜백함수는 일반적으로 task Que가 아니라 우선순위가 더 높은 Animation Frames란 Que에 등록됨. 그리고 호출 스택이 비었을 때 큐에서 하나씩 가져오는 것이 아니라 Animation Frames Que 에 쌓여있는 모든 함수를 꺼내와서 실행하기에 더욱 부드러운 애니메이션 효과를 적용가능



브라우저 렌더링 단계의 이해


Raf 의 동작원리를 이해하기 위해서는 우선 브라우저의 렌더링 과정을 이해할 필요가 있다.

기본적으로 브라우저는 위와 같은 단계를 거쳐서 화면에 무언가를 보여준다.
간략히 정리해서 말하자면

  1. javascript 실행 단계에서 DOM 생성
  2. CSS 규칙 및 적용대상에 대한 계산 프로세스 (CSSOM 생성)
  3. DOM, CSSOM 을 결합하여 각 객체들의 위치, 크기를 계산하는 render tree 생성
  4. 브라우저에서 render tree 를 통해 실제 화면에 그려지는 단계 진행 (Paint)
  5. 각 객체들을 합쳐 최종적인 화면을 생성 (Composite)

이 정도의 단계를 통해 최종적으로 화면 렌더링이 이루어진다고 이해할 수 있다.



브라우저 렌더링 - 프레임의 이해


화면에서 무언가 부드럽게 움직이는 것처럼 보이는 것은 사실 그렇게 보일 수 있도록 초당 수십번의 화면을 각 진행정도에 따라 연속적으로 보여주며 업데이트하기 때문에 그렇게 보이는 것이다.

여기서 모니터 주사율과 fps 개념을 접목해볼 수 있다. 보통 60hz, 120hz 같은 모니터 주사율을 쉽게 들어볼 수 있는데 이는 해당 기기가 1초동안 화면을 출력하는 빈도를 나타내는 단위인 것이다. Frame도 비슷한 의미로 그 뜻은 한 장의 사진으로 이해해도 무방하다. 그래서 fps (frame per seconds) 는 초당 보여지는 사진 장의 수를 의미하며 이는 일반적으로 60으로 맞춰진다.
(사람의 눈이 초당 60번의 장면이 변경되어 넘어갈 때 부드러운 전환이라고 인지하기 때문)


즉, 브라우저 화면상에서 무언가 부드러운 애니메이션효과를 주고자 한다면 위에 설명한 이 초당 60번의 프레임에 맞춰서 설계되어야 한다고 볼 수 있다. 쉽게 말해 16.666ms (1000ms / 60fps) 마다 주기적으로 화면 렌더링 관련 코드가 실행되어야 한다는 것.



setInterval, setTimeout 을 이용한 애니메이션 동작의 문제점

  • 물론 쉽게 생각해서 위의 주기적인 동작을 javascript에서 제공하는 타이머 함수로 구현해 볼 수도 있다.
    예시로 setInterval 의 트리거 주기를 16.666ms로 맞추고 원하는 화면 렌더링 동작을 콜백 내부에 추가한다면 화면 렌더링 주기에 맞춰 setInterval 또한 반복적으로 실행되면서 애니메이션 효과를 줄 수 있을 것이다.

    다만 여기에는 JS의 특성으로 인해 불가피한 문제 가능성이 존재한다

  • 기본적으로 setTImeout, setInterval 같은 함수는 wep API 다. 이런 함수들은 실행될 때 콜스택에 쌓여서 바로 실행되는 것이 아니라 wep API 별로 따로 담기고, 각 내부의 콜백함수들은 다시 Que 로 이동되어 콜스택이 비어있게 될 때까지 대기상태가 된다.

  • 즉, 복잡한 코드 내에서 타이머 함수 실행 후 다른 여러 함수들도 실행된다면 이후 실행된 함수들이 콜스택에 쌓여 이를 우선처리해야 하기에 Que에 있는 콜백함수들은 대기상태가 되고, 이로 인해 정확히 16.66ms 마다 실행되는 것이 아닌 중간에 blocking이 되면서 일부 프레임에 대한 화면전환 코드가 실행되지 못해 프레임 드랍이 일어나게 된다.
    (이런 프레임 드랍은 곧 매끄럽지 못한 애니메이션 전환을 보여주게 된다)



    정리하자면, 타이머 함수같은 Web API 들은 여러 스택이 중첩되는 상황에서는 바로 실행되지 못하기에 프레임 드랍이 일어날 수 있다. 즉 항상 일관되게 주기적으로 렌더링 주기에 맞춰 실행될 것이라는 보장을 할 수 없다.



문제해결의 방안 - requestAnimationFrame 활용

  • 위 같은 문제를 해결할 수 있는 대체 방안으로 사용할 수 있는 것이 바로 requestAnimationFrame 이다. raf는 시스템이 프레임을 그릴 준비가 되면 animationFrame을 호출한다. 실제 화면이 갱신되어 표시되는 주기에 따라 함수를 호출해주기 때문에 자바스크립트가 프레임 시작 시 실행되도록 보장해주어 위와 같은 밀림 현상을 방지해주고 항상 일관되게 정확한 주기대로 프레임에 맞춰 코드실행이 됨을 보장한다.



Raf 의 장점


초반에 간략히 설명한 raf의 장점들에 대해 좀 더 자세하게 알아보자. 큰 목차로 나눠보자면 이렇다.


백그라운드에서 동작을 중지

  • 다른 탭화면을 보거나 창을 최소화하게 되면 현재 보던 페이지는 비활성상태가 되는데, 이 경우 raf는 주기적으로 화면을 렌더링하던 작업을 일시중지하게 된다. 이로 인해 불필요한 CPU 연산이나 배터리 수명을 절약할 수 있다.

각 디스플레이의 주사율에 맞게 주기적으로 호출

  • 앞서 setInterval 의 경우 주사율을 16.666ms(60hz)로 직접 정의하고 반복 실행해주었다. raf 는 이와 달리 현재 디스플레이의 주사율을 그대로 따른다. 웹브라우저는 디스플레이의 주사율을 따르며 만일 144hz 주사율 고사양 모니터일 경우 rAF 역시 1초에 144번 호출되게 되므로 따로 정의해 줄 필요가 없다.

Animation frames 큐에서 처리

  • 일반적인 비동기 함수의 콜백처리처럼 Stack Que 에 쌓이는 것이 아니라 animation Frame 이라는 별도의 Que 에 콜백함수가 쌓이게 된다. 이로 인해 Task Que 에 쌓이는 콜백들에 상관없이 이벤트 루프 과정에서 animation Frame 의 Que 에 쌓여있는 모든 함수를 한 번에 가져와서 콜 스택으로 옮겨 실행하기 때문에 코드 실행의 밀림현상을 최소화 할 수 있다.
    (물론 밀림현상을 항상 방지해주는 것은 아니며 CPU나 GPU 사용량 여부 등에 따라 콜백 함수 실행이 밀릴 수도 있다)



+a) raf 의 타이머 인수 활용

  • requestAnimationFrame은 내부에 인자로 전달하는 콜백함수의 인자로 해당 raf 가 실행되는 시점의 시간대를 받아올 수 있다.


function animate(time) {
  console.log(time); // 매 frame (raf 호출) 의 실행 시간
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);
  • 이 값을 활용해 특정 시간대에 도달하면 raf를 종료하거나 performance.now 같은 메서드로 최초시작 시간 값에 대해 매 프레임의 시작 시간대를 연산하여 애니메이션 전환의 변경값으로 사용하는 식으로 활용이 가능하다.

    참고자료 1 - (raf 사용법 등 상세한 정보)
    참고자료 2 - (raf의 개념에 대한 간략정리)
profile
막 발걸음을 뗀 신입

0개의 댓글