The #ChromeDevSummit site is live, happening Nov 12-13 in San Francisco, CA
Check it out for details and request an invite. We'll be diving deep into modern web tech & looking ahead to the platform's future.

스타일 계산의 범위와 복잡성 줄이기

DOM 변경, 요소 추가 및 제거, 속성 변경, 클래스 또는 애니메이션은 모두 브라우저가 요소 스타일을 재계산하고 많은 경우에 페이지 또는 페이지 일부를 레이아웃(또는 리플로우)하게 만듭니다. 이 프로세스를 컴퓨팅 스타일 계산이라고 합니다.

컴퓨팅 스타일의 첫 부분은 매칭 선택기 집합을 생성하는 것입니다. 이는 기본적으로 브라우저가 주어진 요소에 적용되는 클래스, 의사 선택기 및 ID를 계산하는 것입니다.

프로세스의 두 번째 부분에는 매칭 선택기에서 모든 스타일 규칙을 가져와서 요소의 마지막 스타일을 계산하는 과정이 포함됩니다. Blink(Chrome 및 Opera의 렌더링 엔진)에서 이 두 프로세스의 비용은 현재 비슷합니다.

요소의 컴퓨팅 스타일을 계산하는 데 사용되는 시간의 약 50%는 선택기를 매칭하는 데 사용되고 나머지 절반은 매칭하는 규칙에서 RenderStyle(컴퓨팅 스타일 표현)을 생성하는 데 사용됩니다. Rune Lillesveen, Opera / Blink에서 스타일 무효화

TL;DR

  • 선택기의 복잡성을 줄이고 BEM과 같은 클래스 중심의 방법론을 사용합니다.
  • 스타일을 계산해야 하는 요소 수를 줄입니다.

선택기의 복잡성 줄이기

가장 간단한 경우 한 클래스만 있는 CSS의 요소를 참조합니다.

.title {
  /* styles */
}

하지만 프로젝트가 커짐에 따라 CSS가 더 복잡해지고 다음과 같은 선택기로 끝날 수 있습니다.

.box:nth-last-child(-n+1) .title {
  /* styles */
}

스타일을 적용할 필요가 있는지 알기 위해 브라우저는 다음과 같은 질문을 해야 합니다. “이 요소는 상자 클래스를 포함한 - n번째 하위 요소 + 1 요소인 상위 요소를 가진 제목 클래스를 포함한 요소인가?” 사용하는 선택기와 브라우저에 따라 이를 계산하는 데 많은 시간이 걸릴 있습니다. 대신 선택기의 의도된 동작이 클래스로 변경될 수 있습니다.

.final-box-title {
  /* styles */
}

클래스의 이름이 문제가 될 수 있지만 브라우저에서 작업이 휠씬 간단해집니다. 이전 버전에서 예를 들어, 요소가 해당 유형의 마지막인지 알기 위해, 브라우저는 먼저 모든 다른 요소에 대해 모든 것을 알아야 하고, n번째 마지막 하위 요소 뒤에 요소가 있는지 여부를 알아야 합니다. 이는 클래스가 매칭하기 때문에 잠재적으로 단순히 선택기를 요소에 매칭하는 것보다 휠씬 많은 비용이 들 수 있습니다.

스타일 지정 요소 수 줄이기

또 다른 성능 고려사항은 일반적으로 많은 스타일 업데이트의 더욱 중요한 요소 로, 요소 변경 시 수행해야 하는 순수한 작업량입니다.

일반적으로, 각 요소는 매칭 여부를 확인하기 위해 최소한 한 번은 모든 스타일에 대해 확인할 필요가 있기 때문에, 요소의 컴퓨팅 스타일 계산 비용의 최악의 사례는 요소 수와 선택기 수를 곱하는 것입니다.

참고: 예전에는 본문 요소에서 클래스를 변경한 경우 페이지의 모든 하위 요소에서 컴퓨팅 스타일을 재계산해야 했습니다. 다행히 이러한 경우는 더 이상 없습니다. 대신 일부 브라우저는 변경 시 요소의 스타일을 재계산해야 하는 각 요소에 고유한 작은 규칙 컬렉션을 유지합니다. 즉, 트리에서 요소의 위치, 구체적인 변경 사항에 따라 요소 재계산이 필요하거나 필요하지 않을 수 있습니다.

대개 스타일 계산은 페이지 전체를 무효화하지 않고 몇몇 요소를 직접 대상으로 할 수 있습니다. 최신 브라우저에서는 잠재적으로 변경의 영향을 받는 모든 요소를 브라우저가 반드시 확인할 필요가 없기 때문에 문제 발생의 소지가 훨씬 적습니다. 하지만 이전 브라우저는 이러한 작업에 맞게 최적화되지 않았습니다. 가급적 무효화되는 요소 수를 줄여야 합니다.

참고: 웹 구성요소를 들여다보면 기본적으로 스타일은 Shadow DOM 경계를 가로지르지 않고 트리 전체가 아니라 개별 구성요소 범위에 포함되기 때문에 여기에서 스타일 계산이 약간 다르다는 것을 알 수 있습니다. 하지만 전반적으로 동일한 개념이 적용됩니다. 즉, 더 간단한 규칙을 지닌 더 작은 트리가 큰 트리나 복잡한 규칙보다 더 효율적으로 처리됩니다.

스타일 재계산 비용 측정

스타일 재계산 비용을 가장 쉽게 측정하는 최선의 방법은 Chrome DevTools의 Timeline 모드를 사용하는 것입니다. 시작하려면 DevTools를 열고 Timeline 탭으로 가서 레코드를 선택하고 사이트와 상호작용합니다. 레코딩을 중단하면 아래와 유사한 이미지가 나타납니다.

DevTools에서 오래 실행되는 스타일 계산 표시

상단의 스트립은 초당 프레임(fps)을 나타냅니다. 하단의 60fps 라인 위에 막대가 나타나면 오래 실행되는 프레임이 있습니다.

Chrome DevTools에서 문제 영역 확대

스크롤 등과 같은 상호작용 동안 오래 실행되는 프레임이 있으면 추가 확인이 필요합니다.

위의 경우처럼 큰 자주색 블록이 있는 경우 레코드를 클릭하면 더 자세한 정보를 볼 수 있습니다.

오래 실행되는 스타일 계산에 대한 세부정보 얻기

위의 이미지에는 18ms 이상 걸리는 오래 실행되는 Recalculate Style 이벤트가 있습니다. 이 이벤트는 스크롤 동안 발생하며 뚜렷한 떨림 현상을 유발합니다.

이벤트를 클릭하면 자바스크립트에서 스타일 변경 트리거를 담당하는 장소를 가리키는 호출 스택이 제공됩니다. 또한 변경의 영향을 받은 요소 수와(이 경우 400여 개 요소) 스타일 계산을 수행하는 데 걸린 시간도 제공됩니다. 이 정보를 사용하여 코드에서 수정할 부분을 찾기 시작할 수 있습니다.

블록, 요소, 한정자 사용

BEM(블록, 요소, 한정자)과 같은 코딩 접근방식은 실제로 위의 선택기 매칭 성능 이점에서 구현됩니다. 그 이유는 모두 단일 클래스를 갖고 계층이 필요한 경우 클래스의 이름으로 구현되기 때문입니다.

.list { }
.list__list-item { }

위와 같이 마지막 하위 요소에 대해 무언가 특별한 작업을 수행하려고 할 때 한정자가 필요한 경우, 다음과 같이 한정자를 추가할 수 있습니다.

.list__list-item--last-child {}

BEM은 구조적 관점에서뿐만 아니라 스타일 조회의 단순함 때문에 CSS를 구성하는 훌륭한 방법으로 활용할 수 있습니다.

BEM 외에도 CSS에 대한 다른 접근방식이 있지만, 인간공학과 성능을 고려하여 선택해야 합니다.

리소스