방법론으로 스타일을 질서 있게 유지하는 법까지 익히면서, 나는 이제 스타일을 쓰고 정리하는 데는 어느 정도 자신이 생겼다. 그런데 잘 짠 스타일이 어떤 기기에서는 버벅이는 걸 보고, 아직 배울 것이 남았음을 알았다. 화면이 부드럽게 그려지는 것은 저절로 되는 일이 아니라, 브라우저가 화면을 어떻게 그리는지를 이해하고 배려해야 얻어지는 결과였다.


이번 편은 CSS 시리즈의 마지막으로, 스타일의 성능과 피해야 할 안티패턴을 다룬다. 브라우저가 화면을 그리는 과정과 그 비용, 화면 밖 내용의 렌더링을 건너뛰는 기능, 렌더링을 격리해 최적화하는 기능, 그리고 실무에서 흔히 저지르는 안티패턴들까지 살펴본다. 마지막으로 지금까지의 여정을 돌아보며 CSS를 마무리한다. 성능을 이해하면 스타일이 모든 기기에서 매끄러워진다.

1. 브라우저가 화면을 그리는 비용

스타일 성능을 이해하려면 브라우저가 화면을 그리는 과정을 알아야 한다. 크게 배치를 계산하는 단계, 픽셀을 칠하는 단계, 그리고 칠해진 것들을 합쳐 화면에 올리는 단계로 나뉜다. 나는 이 세 단계를 이해하고 나서, 어떤 속성이 비싸고 어떤 속성이 싼지를 논리적으로 판단할 수 있게 되었다. 성능은 이 단계들과의 관계에서 결정된다.


가장 비싼 것은 배치를 다시 계산하게 만드는 변경이다. 요소의 크기나 위치를 바꾸면, 그 변경이 주변 요소들의 배치에까지 영향을 미쳐 연쇄적으로 다시 계산해야 한다. 나는 너비나 위치 속성을 자주 바꾸는 코드가 화면을 무겁게 만드는 걸 여러 번 겪었다. 배치 계산은 한 요소에 그치지 않고 퍼져나가기 때문에 특히 비싸다.


그다음으로 비싼 것은 픽셀을 다시 칠하는 것이다. 배치는 그대로여도 색이나 그림자가 바뀌면 해당 영역을 다시 칠해야 한다. 배치 계산보다는 싸지만, 넓은 영역을 자주 칠하면 역시 부담이 된다. 나는 큰 영역의 색이나 배경을 빈번히 바꾸는 것을 조심한다. 다시 칠하는 영역이 넓을수록 비용이 커지기 때문이다.


가장 싼 것은 이미 그려진 것을 합치는 단계에서 처리되는 변경이다. 앞서 여러 번 강조한 변형과 투명도가 여기에 속한다. 이 둘은 배치나 칠하기를 건드리지 않고, 마지막 합성 단계에서만 조정된다. 나는 움직임이 필요할 때 되도록 이 단계에서 처리되는 속성만 쓴다. 같은 효과라도 어느 단계에서 처리되느냐에 따라 비용이 열 배 넘게 차이 날 수 있다.


이 원리는 특히 애니메이션에서 결정적이다. 애니메이션은 짧은 시간에 화면을 여러 번 다시 그리므로, 각 프레임의 비용이 조금만 커도 전체가 버벅인다. 나는 애니메이션할 속성을 고를 때, 그것이 배치 계산이나 넓은 칠하기를 유발하는지를 반드시 따진다. 부드러운 애니메이션은 예쁜 곡선이 아니라, 비용이 싼 속성 선택에서 나온다.


성능을 챙기는 가장 확실한 방법은 실제로 측정하는 것이다. 개발 도구로 화면을 그리는 과정을 관찰하면, 어디서 배치 계산이 반복되는지가 드러난다. 나는 추측으로 최적화하지 않고, 측정으로 병목을 찾은 뒤 그곳만 고친다. 문제가 없는 곳을 미리 최적화하는 것은 시간 낭비이자 코드를 복잡하게 만드는 지름길이다.

2. 화면 밖 내용의 렌더링 건너뛰기

긴 페이지에서는 화면에 보이지 않는 부분까지 브라우저가 모두 그리느라 초기 로딩이 느려진다. 사용자가 아직 보지도 않은 저 아래 내용을 미리 다 그리는 것이다. 나는 아주 긴 페이지가 처음 뜰 때 느린 이유가 여기에 있다는 걸 알았다. 보이지 않는 것까지 그리는 낭비를 줄일 방법이 필요했다.


이를 해결하는 기능이 있다. 요소가 화면 밖에 있으면 그 내부의 렌더링을 건너뛰도록 지정하는 것이다. 사용자가 스크롤해서 그 요소가 화면에 가까워지면 그때 비로소 그린다. 나는 긴 목록이나 여러 구획으로 이루어진 페이지에 이 기능을 적용해, 처음에는 화면에 보이는 부분만 그리게 한다. 초기 로딩이 눈에 띄게 빨라진다.


이 기능을 쓸 때는 요소의 예상 크기를 함께 알려주는 것이 중요하다. 렌더링을 건너뛴 요소의 크기를 브라우저가 모르면, 스크롤 막대의 길이가 부정확해지고 스크롤 위치가 튀는 문제가 생긴다. 나는 건너뛸 요소에 대략적인 예상 높이를 지정해서, 아직 안 그린 요소도 적당한 공간을 차지하게 한다. 그러면 스크롤이 안정적으로 유지된다.


이 기능은 레이아웃이 안정된 긴 영역에 특히 잘 맞는다. 글 목록이나 긴 문서의 구획들처럼, 각 부분의 크기를 어느 정도 예측할 수 있는 곳이다. 나는 이런 영역에 이 기능을 적용해, 수백 개의 항목이 있어도 초기 로딩이 가볍게 유지되도록 한다. 화면에 보이는 것만 그리는 것은, 긴 페이지의 성능을 지키는 가장 효과적인 방법 중 하나다.


다만 이 기능을 아무 데나 쓰면 안 된다. 내부에서 크기를 재야 하는 스크립트가 있거나, 화면 안팎을 자주 오가는 요소에는 오히려 부작용이 생길 수 있다. 나는 이 기능을 크고 안정적인 영역에만 신중하게 적용한다. 최적화 기능일수록 어울리는 상황을 가려서 써야, 이득만 얻고 부작용을 피할 수 있다.


이런 기능은 예전에 자바스크립트로 복잡하게 구현하던 것을 스타일 속성 하나로 대체한다. 화면 밖 요소를 감지해 그리고 지우는 로직을 직접 짜던 시절과 비교하면 놀라운 발전이다. 나는 브라우저가 기본으로 제공하는 이런 최적화를 우선 활용한다. 직접 구현한 최적화보다, 브라우저에 내장된 최적화가 대개 더 안정적이고 효율적이기 때문이다.

3. 렌더링을 격리해 최적화하기

또 하나의 성능 도구는 요소의 렌더링을 주변과 격리하는 것이다. 어떤 요소가 자기 내부에서만 완결된다고 브라우저에 알려주면, 브라우저는 그 요소를 독립된 영역으로 취급해 최적화한다. 나는 이 격리를 통해, 한 요소의 변경이 페이지 전체의 재계산을 유발하지 않도록 범위를 좁힐 수 있었다.


격리의 원리는 변경의 파급을 가두는 것이다. 격리된 요소 안에서 무언가 바뀌어도, 그 영향이 요소 밖으로 새어 나가 주변을 다시 계산하게 만들지 않는다. 나는 자주 변하는 독립적인 컴포넌트에 이 격리를 적용한다. 그러면 그 컴포넌트가 아무리 자주 바뀌어도, 재계산 비용이 그 안에 갇혀 페이지 전체로 번지지 않는다.


이 격리는 특히 반복되는 많은 요소에 효과적이다. 수많은 카드가 있는 목록에서 각 카드를 격리해두면, 한 카드의 변경이 다른 카드들의 재계산을 부르지 않는다. 나는 큰 목록의 항목들에 이 격리를 적용해, 목록 전체의 렌더링 부담을 줄인다. 독립적인 조각들을 서로 격리하면, 전체 성능이 각 조각의 합보다 훨씬 가벼워진다.


격리에는 여러 수준이 있어서, 배치만 격리할지 칠하기까지 격리할지를 고를 수 있다. 나는 상황에 맞는 수준을 선택하되, 대개는 여러 격리를 한꺼번에 적용하는 간편한 방식을 쓴다. 격리 수준을 세밀하게 고민하기보다, 독립적인 컴포넌트라면 통째로 격리하는 편이 실무에서는 대개 충분하다.


브라우저에 곧 변경이 일어날 것을 미리 알리는 힌트도 있다. 이 힌트를 주면 브라우저가 해당 요소를 미리 별도로 준비해둔다. 다만 이 힌트를 상시로 남발하면 메모리를 낭비하고 오히려 성능을 해친다. 나는 이 힌트를 정말 무거운 변경 직전에만 최소한으로 쓰고, 변경이 끝나면 거둬들인다. 최적화 힌트는 필요한 순간에만 켜는 것이 원칙이다.


이런 격리와 힌트는 모두 강력하지만, 남용하면 독이 된다는 공통점이 있다. 격리된 층이 너무 많아지면 메모리를 잡아먹고, 힌트를 상시로 켜두면 자원을 낭비한다. 나는 이런 도구들을 측정으로 확인한 실제 병목에만 적용한다. 최적화는 문제가 있는 곳을 고치는 것이지, 모든 곳에 예방적으로 뿌리는 것이 아니다.

4. 흔히 저지르는 안티패턴

성능만큼 중요한 것이 안티패턴을 피하는 것이다. 가장 악명 높은 안티패턴은 우선순위를 강제로 이기게 만드는 표식의 남용이다. 급할 때는 편하지만, 한번 쓰면 그걸 덮으려고 또 쓰게 되어 결국 문서 전체가 강제 표식의 전쟁터가 된다. 나는 이 표식을 최후의 수단으로만 남겨두고, 우선순위 문제는 원인을 찾아 해결한다.


선택자를 필요 이상으로 길고 강하게 쓰는 것도 흔한 안티패턴이다. 정확히 겨냥하겠다고 조상을 줄줄이 붙이면, 그 순간엔 잘 맞지만 나중에 재사용과 덮어쓰기가 모두 어려워진다. 나는 되도록 짧은 선택자로 겨냥하고, 겨냥이 어려우면 마크업에 클래스를 하나 더 붙이는 쪽을 택한다. 선택자의 강함은 미래의 유연함을 갉아먹는 대가를 치른다.


움직임을 과도하게 넣는 것도 피해야 할 패턴이다. 모든 것에 전환과 애니메이션을 걸면, 화면이 산만해지고 성능도 나빠진다. 나는 움직임을 사용자의 행동에 대한 반응으로 절제해서 쓰고, 비용이 싼 속성만 애니메이션한다. 그리고 움직임에 민감한 사용자를 위한 배려를 항상 함께 둔다. 화려함보다 편안함과 성능이 먼저다.


고정된 값을 여기저기 반복해 흩뿌리는 것도 안티패턴이다. 같은 색이나 간격을 곳곳에 직접 적으면, 값을 바꿀 때 그 모든 곳을 찾아 고쳐야 하고 언젠가 서로 어긋난다. 나는 반복되는 값을 변수로 모아 한 곳에서 관리한다. 값의 단일한 원천을 두는 것이, 일관성과 유지보수를 함께 지키는 길이다.


접근성을 해치는 패턴도 경계해야 한다. 포커스 표시를 지우거나, 사용자의 확대를 막거나, 색만으로 정보를 전달하는 것들이다. 나는 미관을 위해 접근성을 희생하는 선택을 하지 않는다. 포커스 표시는 다듬되 없애지 않고, 크기는 상대 단위로 두어 확대를 존중한다. 아름다움과 접근성은 대립하지 않으며, 둘 다 지킬 수 있다.


이 모든 안티패턴의 공통점은 당장은 편하지만 나중에 비용을 치른다는 것이다. 나는 코드를 쓸 때 지금의 편함이 아니라 나중의 유지보수를 생각하려 한다. 강제 표식의 유혹, 강한 선택자의 편함, 과한 움직임의 화려함은 모두 미래의 나에게 청구서를 보낸다. 좋은 스타일은 지금 조금 더 신중한 대가로, 오래도록 다루기 쉬운 코드를 남긴다.

5. CSS 여정을 마치며

이 편으로 CSS 시리즈를 마무리한다. 돌아보면 적용 원리와 우선순위에서 시작해, 박스 모델과 배치, 색과 타이포그래피, 단위와 반응형, 움직임과 변형, 변수와 선택자, 겹침과 넘침, 레이아웃 패턴과 방법론, 그리고 성능까지 긴 길을 걸어왔다. 나는 이 여정에서 CSS가 단순히 꾸미는 언어가 아니라, 나름의 깊은 원리를 가진 체계임을 배웠다.


이 시리즈를 관통하는 하나의 태도가 있다면, 원리를 이해하려는 자세다. 스타일이 안 먹을 때 값을 무작정 바꾸는 대신 우선순위와 캐스케이드를 따지고, 겹침이 안 될 때 값을 키우는 대신 쌓임 맥락을 살피고, 화면이 버벅일 때 감으로 고치는 대신 렌더링 과정을 이해하는 것이다. 나는 원리를 아는 것이 수많은 편법보다 강하다는 걸 반복해서 확인했다.


또 하나 강조하고 싶은 것은 최신 표준의 힘이다. 이 시리즈에서 다룬 많은 기능들은 예전에 복잡한 편법이나 자바스크립트로 해결하던 문제들을, 스타일 속성 하나로 대체한다. 나는 브라우저가 기본으로 제공하는 기능을 우선 활용하는 것이, 직접 구현하는 것보다 대개 더 안정적이고 효율적임을 배웠다. 표준의 발전을 따라가는 것 자체가 실력이다.


그리고 이 시리즈 내내 반복된 두 가지 가치가 있다. 접근성과 유지보수다. 화려한 화면보다 누구나 쓸 수 있는 화면이 먼저이고, 지금 편한 코드보다 나중에 다루기 쉬운 코드가 낫다. 나는 이 두 가치를 코드의 모든 결정에 스며들게 하려 애쓴다. 좋은 스타일은 보기에만 좋은 것이 아니라, 모두에게 열려 있고 오래 유지되는 것이다.


CSS는 배울수록 겸손해지는 언어다. 단순해 보이지만 깊이가 있고, 다 안다고 생각하는 순간 새로운 표준이 나온다. 나는 이 시리즈로 기초를 다졌지만, 여기가 끝이 아니라 시작이라고 생각한다. 원리를 이해하는 태도만 갖추면, 앞으로 나올 어떤 새 기능도 스스로 익힐 수 있다. 기초가 튼튼하면 변화는 위협이 아니라 새로운 도구일 뿐이다.


긴 CSS 여정을 함께해 준 것에 감사한다. 스타일이 안 먹어 답답하던 순간들이, 이제는 원리로 진단할 수 있는 문제로 바뀌었기를 바란다. 화면을 아름답고 견고하며 모두에게 열린 것으로 만드는 일은, 작은 원리들이 쌓여 이루어진다. 나는 이 시리즈가 그 원리들의 든든한 토대가 되었기를 바란다. CSS의 세계에서, 이제는 스스로 길을 낼 수 있을 것이다.