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

JavaScript는 종종 시각적 변화를 촉발합니다. 어떤 경우에는 스타일 조작을 통해 직접 변경하거나, 어떤 경우에는 데이터 검색 또는 정렬과 같은 시각적 변화를 일으키는 계산을 통해 변경합니다. 타이밍이 나쁘거나 실행 시간이 긴 JavaScript는 성능 문제의 일반적인 원인이 될 수 있으므로 가능한 경우 영향을 최소화할 수 있는 방법을 찾아야 합니다.

스타일 계산

요소 추가 및 삭제, 속성 변경, 클래스 변경, 애니메이션 재생으로 DOM을 변경하면 브라우저에서 요소 스타일 및 대부분의 경우 페이지 일부 또는 전체의 레이아웃을 다시 계산합니다. 이 프로세스를 계산된 스타일 계산이라고 합니다.

브라우저는 특정 요소에 적용되는 클래스, 의사 선택기, ID를 결정하는 매칭 선택기 집합을 만들어 스타일을 계산하기 시작합니다. 그런 다음 매칭 선택기에서 스타일 규칙을 처리하고 요소의 최종 스타일을 파악합니다.

스타일 재계산 시간 및 상호작용 지연 시간

다음 페인트에 대한 상호작용 (INP)은 사용자 입력에 대한 페이지의 전반적인 응답성을 평가하는 사용자 중심의 런타임 성능 측정항목입니다. 사용자가 페이지와 상호작용하는 시점부터 브라우저가 사용자 인터페이스의 해당 시각적 업데이트를 표시하는 다음 프레임을 그릴 때까지의 상호작용 지연 시간을 측정합니다.

상호작용의 중요한 구성요소는 다음 프레임을 그리는 데 걸리는 시간입니다. 다음 프레임을 표시하기 위해 실행되는 렌더링 작업은 레이아웃, 페인트, 합성 작업 직전에 발생하는 페이지 스타일 계산을 비롯한 많은 부분으로 구성됩니다. 이 페이지에서는 스타일 계산 비용에 중점을 두지만 상호작용과 관련된 렌더링 단계를 줄이면 스타일 계산을 포함한 총 지연 시간도 줄어듭니다.

선택기의 복잡성 줄이기

선택기 이름을 단순화하면 페이지의 스타일 계산 속도를 높일 수 있습니다. 가장 간단한 선택기는 클래스 이름만 있는 CSS의 요소를 참조합니다.

.title {
  /* styles */
}

그러나 프로젝트가 성장함에 따라 더 복잡한 CSS가 필요할 수 있으며 다음과 같은 선택자가 생성될 수 있습니다.

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

이러한 스타일이 페이지에 적용되는 방식을 확인하려면 브라우저는 '이 상위 요소가 box 클래스의 빼기 n번째 + 1 하위 요소인 title 클래스가 있는 요소인가요?'라고 물어봐야 합니다. 사용하는 선택기는 물론 사용하는 브라우저에 따라 시간이 오래 걸릴 수 있습니다. 이를 단순화하기 위해 선택기를 클래스 이름으로만 변경할 수 있습니다.

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

이러한 대체 클래스 이름은 어색해 보일 수 있지만 브라우저 작업을 훨씬 간단하게 만듭니다. 예를 들어 이전 버전에서 브라우저가 요소가 유형의 마지막임을 알 수 있으려면 먼저 다른 모든 요소에 관한 모든 정보를 파악하여 그 뒤에 오는 요소가 nth-last-child일 수 있는지 확인해야 합니다. 이 방법은 클래스가 일치한다는 이유만으로 선택기를 요소에 일치시키는 것보다 계산 비용이 훨씬 많이 들 수 있습니다.

스타일 지정 요소 수 줄이기

또 다른 성능 고려사항이며 선택기 복잡성보다 더 중요한 고려사항은 요소가 변경될 때 발생해야 하는 작업량입니다.

일반적으로 계산된 요소 스타일을 계산하는 데 드는 최악의 비용은 요소 수에 선택기 수를 곱하는 것입니다. 브라우저가 각 요소가 일치하는지 확인하기 위해 적어도 한 번은 모든 스타일에 대해 확인해야 하기 때문입니다.

스타일 계산은 전체 페이지를 무효화하는 대신 일부 요소를 직접 타겟팅할 수 있습니다. 최신 브라우저에서는 변경사항이 영향을 줄 수 있는 모든 요소를 브라우저가 항상 확인할 필요가 없기 때문에 문제 발생 가능성이 적습니다. 반면에 오래된 브라우저는 이러한 작업에 맞게 최적화되지 않을 수도 있습니다. 가능하면 무효화되는 요소 수를 줄여야 합니다.

스타일 재계산 비용 측정

스타일 재계산 비용을 측정하는 한 가지 방법은 Chrome DevTools의 성능 패널을 사용하는 것입니다. 시작하려면 다음 단계를 따르세요.

  1. DevTools를 엽니다.
  2. 실적 탭으로 이동합니다.
  3. 녹화를 클릭합니다.
  4. 페이지와 상호작용합니다.

녹화를 중지하면 다음 이미지가 표시됩니다.

스타일 계산을 보여주는 DevTools
스타일 계산을 보여주는 DevTools 보고서

상단의 스트립은 초당 프레임 수를 표시하는 소형 플레임 차트입니다. 활동이 스트립 하단에 가까울수록 브라우저가 더 빠르게 프레임을 그립니다. 상단에 빨간색 막대와 함께 플레임 차트가 수평으로 정렬되어 있으면 장기 실행 프레임을 유발하는 작업이 있는 것입니다.

Chrome DevTools의 채워진 성능 패널의 활동 요약에서 Chrome DevTools의 문제 영역 확대
DevTools 활동 요약의 장기 실행 프레임입니다.

스크롤과 같은 상호작용 중에 오래 실행되는 프레임은 자세히 살펴볼 필요가 있습니다. 큰 보라색 블록이 표시되면 활동을 확대하고 Recalculate Style 라벨이 지정된 작업을 선택하여 비용이 많이 들 수 있는 스타일 재계산 작업에 관해 자세히 알아보세요.

스타일 재계산 작업의 영향을 받는 요소의 양과 같은 중요 정보를 포함하여 장기 실행 스타일 계산의 세부정보 가져오기
DevTools 요약에서 25ms가 넘게 걸리는 장기 실행 스타일 재계산입니다.

이벤트를 클릭하면 호출 스택이 표시됩니다. 렌더링 작업이 사용자 상호작용으로 인해 발생한 경우 스타일 변경을 트리거한 JavaScript를 호출합니다. 또한 변경사항이 영향을 미치는 요소의 수(이 경우 900개 이상)와 스타일 계산에 걸린 시간을 표시합니다. 이 정보를 사용하여 코드의 수정사항을 찾을 수 있습니다.

블록, 요소, 특수키 사용

BEM (블록, 요소, 수정자)과 같은 코딩 접근 방식은 선택기 매칭 성능 이점을 제공합니다. BEM은 모든 항목에 단일 클래스가 있고 계층 구조가 필요한 경우 이 계층 구조도 클래스 이름에 통합되도록 권장합니다.

.list {
  /* Styles */
}

.list__list-item {
  /* Styles */
}

마지막 하위 요소 예에서와 같이 수정자가 필요한 경우 다음과 같이 수정자를 추가할 수 있습니다.

.list__list-item--last-child {
  /* Styles */
}

BEM은 구조 측면과 스타일 조회 간소화 기능 때문에 모두 CSS를 구성하는 데 좋은 출발점입니다.

BEM이 마음에 들지 않는다면 CSS에 접근하는 다른 방법이 있지만 시작하기 전에 성능과 인체공학을 평가해야 합니다.

자료

Unsplash의 히어로 이미지(Markus Spiske 제작)