스타일과 레이아웃 과정 최적화

2020. 11. 17. 22:09웹 프론트엔드 깊게 이해하기/성능 최적화

(이 글은 아래의 영상 시리즈 속 내용을 바탕으로 제작되었습니다.)

www.youtube.com/watch?v=yJo9lZAEqb0&list=PLAwxTw4SYaPl09X4Rljhy7dZinRCzbHz6

 

 

스타일 과정에서의 처리 시간 증가 곡선

 

자바스크립트나 css 를 통해서 애니메이션 등을 발생시킬 때, 단순히 하나의 요소가 아니라 여러개의 요소의 스타일이 바뀌도록 만들 수 있을 것입니다. 그렇다면 이렇게 스타일 과정에서 변경사항을 반영해야 할 요소들이 많아지면 과연 스타일 과정의 전체 처리시간에 얼마나 큰 영향을 끼치게 될까요? 정답은 '선형적으로 처리시간을 증가시킨다' 입니다. 어떤 스타일 변경을 수행했느냐에 따라 어느 정도는 달라질 수 있습니다. 어쨌든 더 많은 요소의 스타일이 변경되도록 만드는 것은 바람직하지 않다는 것을 명심합시다.

 

 

 

BEM 클래스 명명 규칙의 활용

 

CSS 선택자에는 마치 프로그래밍 하듯 많은 조건을 걸 수 있는 선택자 문법들이 많습니다. 하지만 이들은 단순히 클래스를 매칭시키는 것뿐만 아니라 브라우저에 추가적인 연산을 요구합니다. 따라서 해당 CSS 코드가 정말 많은 요소들에 영향을 끼치게 된다면, 가능하면 그러한 선택자들을 사용하지 않는 것이 좋을 수 있습니다. 그렇기에 BEM 클래스 명명 규칙은 성능 측면에서도 좋은 점이 많습니다.

 

 

 

가장 빠른 CSS 선택자 선택

 

이미 말한바 있지만, CSS는 선택자를 잘 써주는 것만으로도 큰 성능 향상을 이룰 수 있습니다. 몇가지 원칙을 소개하자면

 

  • 가능하면 id, class 를 이용한 속성 명시를 수행하고, tag 에 대한 속성 명시는 수행하지 않는 것이 좋습니다.
  • 자식, 자손 선택자를 최대한 쓰지 않도록 하십시오. 특히, tag 에 대한 자식, 자손 선택자는 끔찍한 성능을 보여줍니다.
  • :not, :nth-child, [] 과 같은 조건을 붙이지 마십시오. 이는 추가적인 연산을 필요로 합니다.
  • 상속을 최대한 활용하십시오. 상속을 최대한 활용하면 CSSOM 트리 자체의 크기를 줄일 수 있습니다.

 

 

 

자식 선택자와 다른 복합 선택자를 통해 복잡한 스타일과 조건을 구현하지 말것

 

우리는 흔히 웹페이지 상의 복잡한 인터랙션을 구현하기 위해 자식이나 자손 선택자, 그리고 수많은 CSS 선택자들을 활용하고는 합니다. 그러면 부모 요소 하나에 클래스를 추가하고, 제거하는 아주 간단한 자바스크립트 코드만으로 해당 인터랙션을 구현할 수 있기 때문입니다. 그러나 이는 성능 측면에서 봤을 때 정말 좋지 않은 방법입니다.

 

.box-container { ... } 
.box-container.toggled > box:nth-child(2n) { background-color: grey; } 
.box { ... } 
const boxContainer = document.queryselector('.box-container'); 
boxContainer.classList.toggle('toggled', boxContainer.contains('toggled'))

 

왜냐하면 단순한 매칭 이외의 작업이 추가되기 때문입니다. 브라우저는 일단 키 선택자를 바탕으로 매칭을 수행하는데, 매칭으로 찾은 키 선택자에 부모 선택자가 있다면 이를 거슬러 올라가면서 정말로 매칭되는 선택자가 맞는지를 알아봅니다. 이 과정에서 추가적이 연산이 매번 반복되는 것입니다.

 

 

 

자바스크립트를 통해 스타일을 바꿀 때의 주의점

 

보통 우리는 자바스크립트를 통해 어느 요소의 스타일을 바꾸게 되면 스타일 과정이 트리거 되고, 만일 그 스타일이 그 요소의 기하학적 특성과 연관이 있는 것이라면, 이에 따라 레이아웃이 수행되리라고 알고 있을 것입니다. 하지만 놀랍게도, 레이아웃은 그저 스타일 속성의 변경으로 일어나지 않습니다. 단순히 그 기하학적 속성을 '읽기'만 하는 것만으로도, 레이아웃을 트리거 시킵니다. 왜냐하면 브라우저는 요소들의 기하학적 특성을 저장하고 있지 않기 때문에 이를 읽으려면 그때마다 새로 계산해야하기 때문입니다. 물론 페인트와 컴포짓까지 다시 수행되지는 않겠지만 레이아웃은 이미 엄청나게 무거운 작업입니다. 어쩔 수 없는 경우가 아니라면 이런 식의 읽기 작업은 지양하거나 최소화하는 것이 좋습니다.

더 끔찍한 일은, 요소의 기하학적인 특성(너비 등)을 읽어서 다른 요소의 기하학적 특성(너비 등)을 바꾸려고 할 때 일어납니다. 이렇게 하면 결과적으로 레이아웃을 2번이나 수행하게 되어버립니다. 이를 'FSL(Force Synchronous Layout)' 이라고 부릅니다. querySelectorAll 과 같은 메서드로 한번에 레이아웃을 수행하던가, 결과 값을 변수에 저장해서 재활용하는 것이 추천됩니다.

 

*참고로 style 과정은 자바스크립트로 인해 style 속성이 변경될 때마다 일어나는 것은 아닙니다. 자바스크립트가 반복문 등을 통해 한번에 여러 스타일을 바꾸면, 브라우저는 이를 모아서 한번에 style 과정에 적용합니다.

 

*많은 사람들이 CSS로 애니메이션을 구현하는 것이 좋은지, 자바스크립트로 구현하는 것이 좋은지를 궁금해합니다. 대부분의 경우, 그것이 요소의 기하학적 속성과 연관이 없는 것이라면 둘다 비슷한 성능을 냅니다. 하지만 아주 복잡한 애니메이션이고 기하학적 속성을 건드리는 것이라면, 자바스크립트로 한땀 한땀 구현하는 것이 좋습니다. 그리고 애니메이션에 관해서라면, 최대한 레이아웃과 페인트를 트리거하지 않는 것이 좋습니다. 둘 다 비용이 매우 크기 때문이며, 페인트는 모바일 기기에서 비용이 정말정말 크기 때문입니다.