불필요한 페인트 피하기

소개

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

회화: 빠르게 둘러보기

브라우저가 수행해야 하는 주요 작업 중 하나는 DOM 및 CSS를 화면의 픽셀로 변환하는 것이며, 이 작업은 상당히 복잡한 프로세스를 거칩니다. 먼저 마크업을 읽고 여기서 DOM 트리를 만듭니다. CSS에서도 비슷한 작업을 하며 CSSOM을 만듭니다. 그런 다음 DOM과 CSSOM이 결합되고, 최종적으로 일부 픽셀을 그리기 시작할 수 있는 구조에 도달합니다.

페인팅 과정 자체가 흥미롭습니다. Chrome에서 DOM 및 CSS의 트리를 결합한 것은 Skia라는 일부 소프트웨어에 의해 래스터화됩니다. canvas 요소를 사용해 봤다면 Skia의 API는 매우 익숙할 것입니다. moveTo 스타일 함수와 lineTo 스타일 함수뿐 아니라 고급 함수도 많이 있습니다. 기본적으로 페인트해야 하는 모든 요소는 실행할 수 있는 Skia 호출 컬렉션으로 정제되며 출력은 여러 비트맵입니다. 이러한 비트맵은 GPU로 업로드되며 GPU는 이러한 비트맵을 합성하여 화면에 최종 그림을 표시합니다.

DOOM을 픽셀로 변환

Skia의 워크로드는 요소에 적용한 스타일의 직접적인 영향을 받는다는 점을 꼭 기억하세요. 알고리즘이 많은 스타일을 사용하는 경우 Skia는 더 많은 작업을 수행해야 합니다. Colt McAnlisCSS가 페이지 렌더링 가중치에 미치는 영향에 관한 도움말을 작성했으므로 자세한 내용을 알아보려면 이 도움말을 읽어보세요.

그렇더라도 페인트 작업을 실행하려면 시간이 걸리며 이를 줄이지 않으면 약 16ms의 프레임 예산을 초과하게 됩니다. 사용자는 프레임이 누락되었음을 알게 되고 이를 버벅거림으로 볼 수 있으며, 이는 결국 앱의 사용자 환경에 악영향을 미치게 됩니다. 우리는 이러한 상황을 원하지 않습니다. 페인트 작업이 필요한 상황과 이에 대해 무엇을 할 수 있는지 알아보겠습니다.

스크롤

브라우저에서 위 또는 아래로 스크롤할 때마다 콘텐츠를 다시 그려야 콘텐츠가 화면에 표시됩니다. 잘 되는 것은 작은 영역이지만 그렇더라도 그려야 하는 요소에 복잡한 스타일이 적용될 수 있습니다. 따라서 페인트할 영역이 작다고 해서 빠르게 색칠할 수 있는 것은 아닙니다.

다시 페인트되는 영역을 확인하려면 Chrome DevTools의 'Show Paint Rectangles' 기능을 사용하면 됩니다. 오른쪽 하단의 작은 톱니바퀴 아이콘을 누르면 됩니다. 그런 다음 DevTools를 연 상태에서 페이지와 상호작용하면 Chrome이 페이지의 일부를 그린 위치와 시점을 깜박이는 직사각형이 표시됩니다.

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

스크롤 성능은 사이트의 성공에 매우 중요합니다. 사용자는 사이트나 애플리케이션이 제대로 스크롤되지 않으면 이를 인지하고 싫어합니다. 따라서 사용자에게 버벅거림이 표시되지 않도록 스크롤 중에 페인트 작업을 밝게 유지하는 데 관심이 있습니다.

앞서 스크롤 성능 관련 도움말을 작성했으니 스크롤 성능의 구체적인 내용을 자세히 알아보려면 이 도움말을 살펴보세요.

상호작용

상호작용은 페인트 작업의 또 다른 원인입니다(마우스 오버, 클릭, 터치, 드래그). 사용자가 이러한 상호작용 중 하나를 실행할 때마다 마우스 오버를 하면 Chrome에서 영향을 받은 요소를 다시 그려야 합니다. 스크롤과 마찬가지로 크고 복잡한 페인트가 필요한 경우 프레임 속도가 떨어집니다.

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

불행한 조합

고가의 페인트를 사용한 데모
고가의 페인트를 사용한 데모

스크롤하면서 마우스를 동시에 움직이면 어떻게 되나요? 요소를 스크롤하면서 의도하지 않게 '상호작용'하여 비용이 많이 드는 페인트를 트리거하는 것이 완벽하게 가능합니다. 결과적으로 약 16.7ms의 프레임 예산 (초당 60프레임을 달성하기 위해 필요한 시간)을 넘길 수 있습니다. 이게 무슨 말인지 정확하게 보여드리기 위해 데모를 제작했습니다. 마우스를 스크롤하고 움직이면 마우스 오버 효과가 나타나기를 바라지만 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를 사용하여 통합해야 합니다.

특히 페인트가 많은 요소에서 우발적인 상호작용은 비용이 많이 들 수 있으며 렌더링 성능을 저하시킬 수 있습니다. 보시다시피 작은 코드를 사용하여 이 문제를 해결할 수 있습니다.

사이트와 애플리케이션을 살펴보세요. 페인트 보호를 조금만 해도 되겠습니까?