불필요한 페인트 피하기

소개

사이트 또는 애플리케이션의 요소를 페인팅하는 것은 비용이 많이 들고 런타임 성능에 부정적인 영향을 미칠 수 있습니다. 이 도움말에서는 브라우저에서 페인팅을 트리거할 수 있는 요소와 불필요한 페인팅이 발생하지 않도록 하는 방법을 간단히 살펴봅니다.

그림: 초고속 둘러보기

브라우저가 실행해야 하는 주요 작업 중 하나는 DOM 및 CSS를 화면의 픽셀로 변환하는 것이며, 이는 상당히 복잡한 프로세스를 통해 이루어집니다. 마크업을 읽는 것으로 시작하여 이를 토대로 DOM 트리를 만듭니다. CSS와 비슷한 작업을 실행하며 CSSOM을 생성합니다. 그런 다음 DOM과 CSSOM이 결합되고 결국 픽셀을 페인트할 수 있는 구조에 도달합니다.

페인팅 과정 자체가 흥미롭습니다. Chrome에서는 DOM과 CSS의 결합된 트리가 Skia라는 소프트웨어에 의해 래스터화됩니다. canvas 요소를 사용해 본 적이 있다면 Skia의 API가 굉장히 익숙할 것입니다. 다양한 moveTolineTo 스타일 함수는 물론 여러 고급 함수도 있습니다. 기본적으로 페인트해야 하는 모든 요소는 실행할 수 있는 Skia 호출 모음으로 추출되며 출력은 비트맵 모음입니다. 이러한 비트맵은 GPU에 업로드되며 GPU는 이를 함께 합성하여 화면에 최종 사진을 표시합니다.

DOM에서 픽셀로

중요한 점은 Skia의 워크로드는 요소에 적용하는 스타일의 영향을 직접 받는다는 것입니다. 알고리즘적으로 무거운 스타일을 사용하면 Skia에서 더 많은 작업을 해야 합니다. 콜트 매컬리스CSS가 페이지 렌더링 웨이트에 미치는 영향에 관한 도움말을 작성했으므로 자세한 내용은 이 도움말을 참고하세요.

하지만 페인트 작업을 실행하는 데 시간이 걸리며, 이 시간을 줄이지 않으면 프레임 예산(약 16ms)을 초과하게 됩니다. 사용자는 프레임을 놓쳤음을 알아차리고 이를 버벅거림으로 인식하여 궁극적으로 앱의 사용자 환경을 저하시키게 됩니다. 이는 바람직하지 않으므로 어떤 종류의 작업으로 인해 페인트 작업이 필요해지는지, 그리고 이를 해결하기 위해 어떤 조치를 취할 수 있는지 살펴보겠습니다.

스크롤

브라우저에서 위 또는 아래로 스크롤할 때마다 콘텐츠가 화면에 표시되기 전에 다시 칠해야 합니다. 문제가 없다면 그리기가 이루어지는 영역은 작지만, 그려야 하는 요소에 복잡한 스타일이 적용될 수 있습니다. 따라서 페인트할 영역이 작다고 해서 빠르게 진행된다는 의미는 아닙니다.

다시 페인트되는 영역을 보려면 Chrome DevTools의 '페인트 직사각형 표시' 기능을 사용하면 됩니다(오른쪽 하단의 작은 톱니바퀴를 클릭). 그런 다음 DevTools를 열고 페이지와 상호작용하기만 하면 Chrome이 페이지의 일부를 그리는 위치와 시점을 깜박이는 직사각형을 볼 수 있습니다.

Chrome DevTools에서 페인트 직사각형 표시
Chrome DevTools에서 페인트 직사각형 표시

스크롤 성능은 사이트의 성공에 매우 중요합니다. 사이트 또는 애플리케이션이 잘 스크롤되지 않으면 사용자는 이를 알아차리고 불만을 느낍니다. 따라서 스크롤 중에 페인트 작업을 가볍게 유지하여 사용자가 버벅거림을 느끼지 않도록 하는 것이 중요합니다.

이전에 스크롤 성능에 관한 도움말을 작성했으므로 스크롤 성능에 관한 세부정보를 자세히 알아보려면 해당 도움말을 참고하세요.

상호작용 수

마우스 오버, 클릭, 터치, 드래그와 같은 상호작용도 페인트 작업의 원인입니다. 사용자가 이러한 상호작용 중 하나(예: 마우스 오버)를 실행할 때마다 Chrome은 영향을 받는 요소를 다시 칠해야 합니다. 스크롤과 마찬가지로 크고 복잡한 페인트가 필요한 경우 프레임 속도가 저하됩니다.

누구나 멋지고 부드러운 상호작용 애니메이션을 원하므로 애니메이션에서 변경되는 스타일이 너무 많은 시간을 소요하는지 다시 확인해야 합니다.

불행한 조합

비싼 페인트를 사용하는 데모
고가 페인트를 사용한 데모

스크롤하면서 마우스를 동시에 움직이면 어떻게 되나요? 스크롤하면서 요소와 실수로 '상호작용'하여 비용이 많이 드는 페인트를 트리거할 수 있습니다. 그러면 초당 60프레임을 달성하기 위해 이 시간 내에 있어야 하는 프레임 예산인 약 16.7ms를 초과할 수 있습니다. 이 내용을 정확하게 이해할 수 있도록 데모를 제작했습니다. 스크롤하고 마우스를 움직이면 마우스 오버 효과가 적용되는 것을 볼 수 있습니다. Chrome DevTools에서 어떻게 표시되는지 살펴보겠습니다.

비용이 많이 드는 프레임을 보여주는 Chrome DevTools
비용이 많이 드는 프레임을 보여주는 Chrome DevTools

위의 이미지에서 블록 중 하나 위로 마우스를 가져가면 DevTools에서 페인트 작업을 등록하는 것을 볼 수 있습니다. 이 점을 강조하기 위해 데모에서 매우 무거운 스타일을 사용했기 때문에 프레임 예산을 초과할 때도 있습니다. 불필요하게 페인트 작업을 해야 하는 것은 최악의 상황입니다. 특히 스크롤 중에 다른 작업을 해야 하는 경우 더욱 그렇습니다.

그렇다면 이러한 일이 발생하지 않도록 하려면 어떻게 해야 하나요? 해결 방법은 매우 간단합니다. 여기서 요령은 마우스 오버 효과를 사용 중지하고 다시 사용 설정하기 위한 타이머를 설정하는 scroll 핸들러를 연결하는 것입니다. 즉, 스크롤할 때 비용이 많이 드는 상호작용 페인트를 실행할 필요가 없습니다. 충분히 오래 중지하면 다시 사용 설정해도 안전하다고 판단됩니다.

코드는 다음과 같습니다.

// Used to track the enabling of hover effects
var enableTimer = 0;

/*
 * Listen for a scroll and use that to remove
 * the possibility of hover effects
 */
window.addEventListener('scroll', function() {
  clearTimeout(enableTimer);
  removeHoverClass();

  // enable after 1 second, choose your own value here!
  enableTimer = setTimeout(addHoverClass, 1000);
}, false);

/**
 * Removes the hover class from the body. Hover styles
 * are reliant on this class being present
 */
function removeHoverClass() {
  document.body.classList.remove('hover');
}

/**
 * Adds the hover class to the body. Hover styles
 * are reliant on this class being present
 */
function addHoverClass() {
  document.body.classList.add('hover');
}

보시다시피 본문에서 클래스를 사용하여 마우스 오버 효과가 '허용'되는지 추적하고 기본 스타일은 이 클래스를 사용하여 표시합니다.

/* Expect the hover class to be on the body
 before doing any hover effects */
.hover .block:hover {
 
}

이제 완료되었습니다.

결론

렌더링 성능은 사용자가 애플리케이션을 즐기기 위해 매우 중요하며 항상 페인트 워크로드를 16ms 미만으로 유지하는 것을 목표로 해야 합니다. 이를 위해 개발 프로세스 전반에서 DevTools를 사용하여 통합하여 병목 현상이 발생할 때마다 이를 식별하고 해결해야 합니다.

특히 페인팅이 많은 요소에서 의도치 않은 상호작용은 비용이 많이 들고 렌더링 성능을 저하시킵니다. 보시다시피 작은 코드 조각을 사용하여 이 문제를 해결할 수 있습니다.

내 사이트와 애플리케이션을 한번 살펴보세요. 페인트를 조금만 보호해도 괜찮을까요?