본문 바로가기

FrontEnd

[번역]CSS 셀렉터 성능 최적화

반응형

CSS 셀렉터가 성능에 미치는 영향을 알아봅니다.

아래 게시물의 번역입니다.

https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/?ck_subscriber_id=1833874137 

 

The truth about CSS selector performance

If you're a web developer, you may have already heard that some CSS selectors are faster than others. And you're probably hoping to find a list of the better selectors to use in this article. Well, not quite. But bear with me, I promise that by the

blogs.windows.com

몇몇 CSS 셀렉터가 다른 것보다 빠르다는 말을 들어본 적이 있을 것입니다
그리고 아마 이 기사에서 더 빠른 셀렉터를 작성하는 방법을 배우길 기대하셨을 지 모릅니다. 

이는 보장할 수 없으나, 해당 기사를 통해 CSS 셀렉터 성능에 대한 새로운 것을 배우게 될 것입니다.

비하인드 스토리 빠르게 살펴보기

CSS 셀렉터를 작성하는 방식은 브라우저가 웹 페이지를 렌더링하는 방식에 영향을 줍니다.
페이지의 일부가 변경될 때마다
해당 페이지를 실행하는 브라우저 엔진은 새 DOM 트리를 살펴보고,
사용 가능한 CSS 스타일시트를 기반으로 스타일을 지정하는 방법을 파악해야 합니다.
스타일과 DOM 노드 사이 대응 관계를 만드는 이 작업을 스타일 재계산이라고 합니다.
 
최대한 너무 많은 세부 사항을 체크하지 않으면서, 브라우저 엔진은 모든 규칙을 살펴보고 주어진 요소에 적용할 규칙을 결정해야 합니다.
이렇게 하려면 엔진이 규칙 셀렉터를 살펴야 하며,
이 탐색은 오른쪽에서 왼쪽으로 발생합니다.
예를 들어 엔진이 `.wrapper .section .title .link`와 같은 셀렉터를 발견하면
먼저 `link` 클래스를 요소와 매칭하려고 시도하고 일치하는 경우 체인을 오른쪽에서 왼쪽으로 위로 이동합니다.
즉, 다음 순서대로 매칭을 시도합니다.
  • 클래스 `title`이 있는 상위 요소
  • `section` 클래스가 있는 요소,
  • 마지막으로 `wrapper` 클래스가 있는 요소를 찾습니다.

이 예는 브라우저 엔진이 이 긴 `.wrapper .section .title .link` 셀렉터를 일치시키는 것보다

`.link`만 일치시키는 것이 더 빠를 가능성이 있음을 보여줍니다.

브라우저가 확인할 사항이 적기 때문입니다


실제로도 이럴까요?

웹 페이지(프로젝트)에 따라, DOM 트리의 크기에 따라, CSS 규칙의 양 및 DOM이 자주 변경되는지 여부에 따라 크게 달라집니다.
불행히도 이에 대한 규칙은 없습니다.

우리는 엔지니어로서 좋은 것과 나쁜 것에 대한 규칙을 발명하는 것을 좋아합니다.
규칙은 빠른 결정을 내리는 데 도움이 되며 코드를 작성하고 소프트웨어를 설계할 때 지침이 됩니다.
하지만, 이 규칙들은 우리의 특정 사례에서 실제로 일어나고 있는 일을 보지 못하게 할 수도 있습니다.
 
CSS 셀렉터를 작성할 때 규칙(rule)을 엄격하게 적용하거나 linter를 사용하여 자동으로 CSS 규칙을 강제하는 것은,
경우에 따라 실제로 비효율적일 수 있습니다.
자주 변경되는 거대한 DOM 트리와 결합된 지나치게 복잡한 CSS 셀렉터는 성능 저하로 이어질 수 있습니다.
그렇지만 어딘가에는 균형점이 존재합니다.
 
더 나은 성능과 규칙 준수를 위해 셀렉터를 변경하는 것은 실질적인 이득 없이
CSS를 읽고 유지하기 어렵게 만들 수 있습니다.

따라서 앱에 적합하고 쉽게 읽고 유지 관리할 수 있는 방식으로 코드를 작성한 다음
중요한 사용자 시나리오의 실제 성능을 측정하세요.


직접 성능 측정해보기

높은 성능의 코드를 작성하는 방법에 대한 일련의 규칙을 맹목적으로 적용하는 것보다 주요 앱 시나리오의 성능을 측정하고, 성능 최적화를 적용하는 것이 좋습니다.
Microsoft Edge DevTools에는 앱이 느리게 느껴지기 시작할 때 눈을 크게 뜨게 해주는 성능 도구가 있습니다.

저는 여기서 느낌이라는 단어를 강조하고 싶습니다.
사용자에 대한 공감대를 형성하고 가능한 경우 사용자가 실제로 사용하는 장치를 사용하십시오.

우리가 개발을 위해 사용하는 시스템은 사용자의 디바이스보다 훨씬 강력할 수 있습니다.

 

DevTools로 할 수 있는 한 가지 좋은 것은 DevtTools 내에서 직접 CPU 및 네트워크 연결 속도를 늦추는 것(slow down your CPU and network connection)입니다.
성능 도구는 상당히 복잡해 보일 수 있지만 도움이 되는 문서(documentation)가 있습니다.
또한 모든 것이 브라우저에서만 발생하므로 아무 것도 손상시키지 않고 시도할 수 있으며
문제가 발생하면 언제든지 페이지를 새로고침하고 DevTools를 다시 열 수 있습니다.

주요 시나리오를 측정하는 데 사용할 수 있는 도구를 사용하는 방법을 배우고
작업을 느리게 만드는 가장 큰 장애물을 식별하는 방법을 배웁tlek.

style recalculation

실제로 스타일 재계산이 앱을 느리게 만드는 원인 중 하나라면 좋은 소식이 있습니다.
성능 문제를 조사할 때 문제의 근본 원인을 즉시 알려주는 도구보다 좋은 것은 없습니다.

셀렉터 통계 활용하기

Microsoft Edge 109부터 DevTools의 성능 도구는 모든 스타일 재계산에서 가장 비용이 많이 드는 셀렉터를 나열할 수 있습니다.
  1. 성능 도구를 엽니다.
  2. 오른쪽 상단 모서리에 있는 톱니 아이콘을 클릭하여 도구 설정을 엽니다.
  3. 고급 렌더링 도구 사용(느림) 옵션을 선택합니다.
  4. 기록을 클릭하고 개선하려는 웹 페이지에서 시나리오를 실행한 다음 중지를 클릭합니다.
  5. 기록된 프로필에서 개선하려는 긴 스타일 재계산을 식별하고 폭포 보기("메인" 섹션)에서 선택합니다.
  6. 하단 탭 표시줄에서 셀렉터 통계를 클릭합니다.

이제 DevTools는 이 재계산 작업 중에 브라우저 엔진에서 계산한 모든 CSS 셀렉터 목록을 제공합니다.
셀렉터를 처리하는 데 걸린 시간이나 셀렉터와 일치하는 DOM 갯수를 기준으로 정렬할 수 있습니다.

Selector Stats

처리하는 데 오랜 시간이 걸리거나, 여러 번 매치된 셀렉터는 개선할 수 있는 좋은 후보가 될 수 있습니다.
셀렉터를 단순화할 수 있습니까? 더 구체적으로 만들 수 있습니까?

이 새로운 기능을 사용하면 의심스러워 보이는 스타일 재계산에서 긴 시간을 유발하는 개별 CSS 셀렉터로 즉시 이동할 수 있습니다.
그런 다음 소스 코드로 돌아가 특정 셀렉터를 개선하고 다시 측정할 수 있습니다.
 

사례 연구

위 설명을 좀 더 실용적으로 만들기 위해 실제 웹 페이지를 개선해 보겠습니다.
이를 위해 아래와 같이 데모용으로 구축된 사진 갤러리 페이지를 사용할 것입니다.

photo gallery page built as a demo
반응형

이 페이지 상단에는 카메라 모델, 조리개 크기, 노출 시간 등으로 사진을 필터링하는 도구 모음이 있으며
카메라 모델 간 전환이 지금은 약간 느리게 느껴집니다.

이 데모 페이지는 Microsoft의 제품에서 발생한 것과 유사한 사례를 보여줍니다.
웹 플랫폼에 의존하는 Microsoft의 Edge 팀과 기타 제품 팀은 최고의 사용자 경험을 만들기 위해 이 영역에서 긴밀하게 협력합니다.
특정 시나리오에서 DOM 요소가 많은 앱(예: 약 5000개의 요소가 있는 여기에서 사용할 데모 페이지)에서
비정상적으로 긴 스타일 재계산이 발생했습니다.
CSS 셀렉터 통계 도구를 사용할 수 있어서 많은 도움이 되었습니다.
우리가 집중할 시나리오는 다음과 같습니다.
  • 데모 페이지를 로드하고 필터가 준비될 때까지 기다립니다.
  • 카메라 모델 필터를 다른 값으로 전환하고 성능 측정을 시작합니다.
  • 모든 카메라 모델로 다시 전환하고 녹화를 중지합니다.

모든 카메라 모델로 다시 전환하는 속도가 느리기 때문에, 해당 부분만 측정합니다.
강력한 개발 환경에서보다 사용자에게 더 가까운 결과를 얻기 위해 CPU 속도를 4배로 낮출 것입니다.

 

기록이 준비되면 프로파일에서 긴 스타일 재계산 블록을 쉽게 볼 수 있으며
제 경우에는 900밀리초 이상의 작업에 해당합니다.
이 블록을 클릭하고 Selector Stats 창을 연 다음 경과 시간별로 정렬해 보겠습니다.

long style recalculation block in the profile

셀렉터를 DOM과 매칭하는데 데 더 많은 작업이 필요하고 일치하는 횟수가 많을수록
이 셀렉터를 개선하여 더 많은 잠재적인 승리를 얻을 수 있습니다.

다음 셀렉터들은 흥미로워 보입니다.

  • .gallery .photo .meta ::selection
  • .gallery .photo .meta li strong:empty
  • [class*=" gallery-icon--"]::before
  • .gallery .photo .meta li
  • *
  • html[dir="rtl"] .gallery .photo .meta li button

::selection 셀렉터 개선하기

데모 웹 페이지에서 `.gallery .photo .meta ::selection`셀렉터를 사용하여
페이지의 사진 메타데이터 부분 내에서 사용가 선택한 사진의 배경 및 텍스트 색상의 스타일을 지정합니다.
사용자가 사진 아래의 텍스트를 선택하면 브라우저 기본 색상 대신 사용자 정의 색상이 사용됩니다.

이 특정 케이스는 코드의 버그 때문에 실제로 문제가 됩니다.
셀렉터는 대신 `.gallery .photo .meta::selection`이어야 합니다.
즉, `.meta`와 `::selection` 사이에 공백이 없어야 합니다.

 

공백이 있기 때문에 셀렉터는 엔진에서 '.gallery .photo .meta *::selection'으로 해석됩니다.
엔진이 모든 DOM 요소를 확인해야 하므로 스타일을 다시 계산하는 동안 돔과 매치하는 속도가 훨씬 느려집니다.
브라우저는 모든 DOM 요소를 체크한 다음 클릭한 다음 올바른 조상(.meta) 내부에 중첩되어 있는지 확인합니다.
 
가운데 공백이 없으면 엔진은 더 진행하기 전에 요소에 '.meta' 클래스가 있는지 확인하기만 하면 됩니다.

::empty 셀렉터 개선하기

셀렉터 `.gallery .photo .meta li strong:empty`는 얼핏 보기에 수상해 보입니다.
`:empty` 의사 셀렉터는 `strong` 요소에 내용이 없을 때만 셀렉터가 일치함을 의미합니다.

 

이렇게 하려면 엔진이 요소의 태그 이름을 확인하는 것보다 약간 더 많은 작업을 수행해야 하지만 매우 유용합니다.
그러나 이와 유사한 다른 CSS 규칙을 보면 다음을 볼 수 있습니다.

.gallery .photo .meta li strong:empty {
  padding: .125rem 2rem;
  margin-left: .125rem;
  background: var(--dim-bg-color);
}

html[dir="rtl"] .gallery .photo .meta li strong:empty {
  margin-left: unset;
  margin-right: .125rem;
}
잘 보면, 동일한 셀렉터가 두 번 반복되고 있습니다.
두 번째 인스턴스에는 페이지의 텍스트 방향이 오른쪽에서 왼쪽일 때 첫 번째 규칙을 재정의하는 데 유용한 'html[dir=rtl]' 접두사가 붙습니다.
이 경우 rtl 방향 규칙은 왼쪽 마진을 무시하고 오른쪽 마진으로 대체합니다.

이를 개선하기 위해 CSS logical properties을 사용할 수 있습니다.
물리적인 마진 방향을 지정하는 대신 아래와 같이 모든 텍스트 방향에 적용되는 논리적 마진을 사용할 수 있습니다.

.gallery .photo .meta li strong:empty {
  padding: .125rem 2rem;
  margin-inline-start: .125rem;
  background: var(--dim-bg-color);
}
논리적 CSS 속성(CSS logical properties)을 사용하여 개선할 수 있는 동일한 속성 셀렉터를 사용하는 CSS 코드의 다른 위치가 있습니다.
예를 들어 앞에서 찾은 `html[dir="rtl"] .gallery .photo .meta li button` 셀렉터를 제거할 수 있습니다.
 
이제 .gallery .photo .meta li strong:empty에 대한 매칭을 두번 수행할 필요가 없습니다.

[class*=" gallery-icon--"] 셀렉터 개선

다음 셀렉터는 복잡해 보이는 속성 셀렉터인 `[class*=" gallery-icon--"]::before`입니다.

속성 셀렉터는 매우 유용할 수 있으므로 제거하기 전에 실제로 부정적인 영향을 미치고 있는지 확인하세요!

이 셀렉터를 사용하는 CSS 규칙은 다음과 같습니다.
[class*=" gallery-icon--"]::before {
  content: '';
  display: block;
  width: 1rem;
  height: 1rem;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filter: contrast(0);
}

.gallery-icon--camera::before { background-image: url(...); }
.gallery-icon--aperture::before { background-image: url(...); }
.gallery-icon--exposure::before { background-image: url(...); }
...
여기서 아이디어는 이러한 아이콘 클래스를 요소에 할당할 수 있고 해당 아이콘을 얻을 수 있다는 것입니다.

이것은 편리한 기능이지만 엔진에 클래스 값을 읽고 서브스트링 문자열 검색을 수행하도록 요청하고 있습니다.
다음은 엔진이 덜 일하도록 도울 수 있는 한 가지 방법입니다.

.gallery-icon::before {
  content: '';
  display: block;
  width: 1rem;
  height: 1rem;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filter: contrast(0);
}

.gallery-icon.camera::before { background-image: url(...); }
.gallery-icon.aperture::before { background-image: url(...); }
.gallery-icon.exposure::before { background-image: url(...); }
이제 하나의 클래스만 사용하는 대신 , 즉 `<div class="gallery-icon-camera">` 대신
두 개의 클래스를 요소에 추가하여`<div class="gallery-icon camera">`를 사용합니다.
이 최적화는 적용하기 매우 쉽고,
데모 페이지처럼 스타일을 변경해야 하는 DOM 노드가 많을 때 엔진 작업을 많이 줄일 수 있습니다.

.gallery .photo .meta li 셀렉터 개선

이 셀렉터는 정말 무해해 보입니다.
그러나 앞에서 설명한 것처럼 여전히 브라우저가 `li` 요소에 대한 조상 목록의 여러 수준을 확인하도록 강제합니다.
웹 페이지에 `li` 요소가 많다면 문제가 됩니다.
`li` 요소에 특정 클래스를 부여하고 불필요한 중첩을 제거하여 이를 단순화할 수 있습니다.
.photo-meta {
  display: flex;
  align-items: center;
  gap: .5rem;
  height: 1.5rem;
}

* 셀렉터 개선

`*` 기호는 모든 요소와 일치하는 CSS의 범용 셀렉터로 사용됩니다.

무엇이든 일치시키는 이 기능은 엔진이 모든 요소에 관련 규칙을 적용해야 함을 의미합니다.

성능 기록에서 볼 수 있듯이 이 선택기는 실제로 여러 번 매칭합니다.

 

CSS 규칙이 실제로 무엇을 하는지 살펴볼 가치가 있습니다.

우리의 경우에는 특정 `box-sizing` 값을 적용합니다.

* {
  box-sizing: border-box;
}
이것은 CSS에서 매우 일반적이지만 실제로는 필요한 경우에만 `box-sizing`을 적용하는 것이 성능면에서 좋습니다.

성능 개선 결과

모든 개선이 완료되었으므로, 시나리오의 성능을 다시 확인할 때입니다.

the same Recalculate Style block that was taking almost a second to run, is now taking around 300ms to run

 
실행하는 데 거의 1초가 걸렸던 동일한 Recalculate Style 블록이 이제 약 300ms가 걸리며 이는 정말 큰 승리입니다!

결론

사례 연구는 특정 CSS 셀렉터를 개선하면 중요한 성능 향상으로 이어질 수 있음을 보여주었습니다.
 
그러나 이것은 특정 사용 사례에 따라 다르다는 점을 기억하는 것이 중요합니다.
성능 도구를 사용하여 웹 페이지의 성능을 테스트하고
스타일 재계산으로 인해 사용자 사용 사례(시나리오가) 느려지는 경우,
Microsoft Edge의 새로운 셀렉터 통계 창을 사용하세요.
 
항상 그렇듯이 DevTools 팀에 ​​대한 피드백이 있으면 GitHub repository리에서 새 이슈를 열어 연락해 주십시오.

추가로 볼 만한 자료

https://yceffort.kr/2021/03/improve-css-performance

 

Home

yceffort

yceffort.kr

 

반응형