티스토리 뷰

0. 왜 이 포스팅을 쓰게되었는지에 대하여.

이 포스팅은 계획에 없던 포스팅이다.

사실 CSS Triggers에 대한 포스팅은 저번 포스팅으로 끝내려고 했었다.

 

근데 왜 또, 포스팅을 작성하고 있냐...면,

사실 내가 숱한 기술 블로그 포스팅들을 보면서 답답했던 부분이, 왜 이론은 엄청 방대하고 자세하게 설명들을 하면서 쉽고 직관적이며 이해하기 쉬운 예제는 잘 안 넣어두시는 걸까, 였었다. 그래서 예시가 있으면 이해가 더 쉬울 것 같은 포스팅에는 그때그때 예시를 될 수 있으면 넣으려고 하는데...그래서 저번 포스팅이었던 [CSS] CSS Triggers 에서도 역시나, CSS Triggers에 대한 쉬운 예제 코드를 넣으려 했었다.

 

근데 아뿔싸, 예상치 못 한 현상을 봐버린거다.

<!-- 대충 1초에 한 번씩 opa 라는 class가 토글되는 코드 -->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>CSS Triggers</title>
    <style>
        .text {
            opacity: 1;
        }
        .opa {
            opacity: 0.3;
        }
    </style>
</head>

<script>
    window.onload = function () {
        setInterval(() => {
            document.getElementById('text').classList.toggle('opa');
        }, 1000);
    }
</script>

<body>
    <div>
        <div id="app" class="app">
            <h1 id="text" class="text">CSS Trigger</h1>
        </div>
    </div>
<div>

그러니까, 대략 이런 예제코드를 바탕으로 opacity라는 CSS 요소를 Trigger로 썼을때...

내가 기대한 건 아래와 같이 CSS Triggers에 따라서 reflow(layout) 단계가 발생하지 않아야되는건데

 

CSS Triggers 사이트의 opacity. 'Blink'에선 reflow가 발생하지 않는다고 되어있다.

 

실제로 개발자도구의 performance탭을 통해서 레코드한 기록을 봤더니

 

Layout 네가 왜 거기서 나와...?

아니, 왜 Layout이 발생...?

opacity는 분명 reflow 단계부터 발생시키는 애가 아닌데...?

 

처음엔 난 내가 뭔가 잘 못 알고있던건가? 싶었다.

내가 그간 알고있던 내용에서도 그렇고, 그 지식을 쌓기위해 공부했을 당시나 최근 리서치 과정에서도 그렇고...분명 opacity는 reflow를 발생하지 않는 CSS 요소값으로 알고있었기 때문이다(이게 포스팅을 작성하는 중요 포인트가 되버리는 이유는...어떻게 보면 겨우 한 가지 속성을 내가 잘 못 알고있었을 수도 있지만, 하나의 오판으로 균열이 가게되서 내가 알고있던 개념이 깨지면, 결국 다른 CSS Triggers 들은 내가 알고있던 게 정확한가? 에 대한 확신이 없어지기 때문이다)

 

혹시 그 사이에 렌더링 엔진이 버전업을 하면서 opacity나 일부 CSS값의 Triggers 시점을 변경했을수도있고...그래서 어제 포스팅을 열심히 작성하다가 작성을 멈췄었다.

 

그리고 좀 더 깊고 명확한 답을 찾기위해 검색과 검색과 검색을 계속해나갔지만,

국내외를 가리지않고 대부분의 글에서 opacity는 reflow가 발생하지 않는다고 하고 있었다.

심지어 어디서는 repaint 마저도 발생하지 않는다, 라고 하고 있었다.

 

답을 찾기 위한 검색이 오히려 독이 되어가는 느낌이랄까.

[CSS] CSS Triggers를 포스팅한 이후로 어제까지 3일은 계속 시간날때마다 찾아봤던 거 같다.

그리고 결국 답을 찾을 수 있었는데...결론은, 전부 맞는말이라는 거다.

 

opacity라는 스타일의 변경은 내가 함정에 빠졌던 윗 스크린샷과 같이 reflow부터 발생하기도하지만, 때로는 repaint부터 발생하기도하고, 또 때로는 reflow&repaint 없이 composite 단계에서 GPU의 도움을 받기도 한다.

 

그리고 이 '때'에는 분명 조건이 있었다.

이해를 돕기위해 Pseudocode로 표현해보자면 대략 아래와 같지않을까, 싶다.

// 실제로 이렇게 동작한다는 건 아니고, 이해를 돕기 위해 작성된 Pseudocode다.
function changeOpacity(value){
  if(composite 발생조건){
    // composite
  } else if(repaint 발생조건){
    // repaint
  } else {
    // reflow
  }
}

기억하자. 우리가 opacity나 transform과 같은 요소를 사용하는 이유는, 최종적으로 브라우져의 연산 비용을 최대한 줄이고자 하는데 있다.

1. 1번 항목이라 1이라는 값이 원인인건가?!

썰을 하나하나 풀기전에, 첫 의문부터 해결해보자면,

opacity의 값을 변경했는데 reflow가 발생한 원인은 주어진 값이 1이라서 그렇다.

app라는 css class에 준, opacity : 1

<style>
  .text{
    opacity: 1; /* 이거...바로 이거 !! */
  }
  .opa{
    opacity: 0.3;
  }
</style>

당연한 말이지만, opacity라는 속성은 화면에 렌더링되는 요소의 투명도를 조절하는데 쓰이는데...해당 값이 1인 상태로 주어졌을 때 값을 변경하면 reflow가 발생한다. 그럼 text class의 opacity를 0.99 로 주고 다시 개발자도구의 Performance 탭을 보면?

 

여담이지만, 제가 어제 세차를 했습니다. 그래서 차가 매우 깨끗해졌답니다. 마치 포포몬쓰 탭처럼요.

짜잔, reflow(layout)도 repaint(paint)도 없이 깨------끗하게 포포몬쓰가 변해버렸다.

 

2. Layer, and Stacking Context

갑자기 이게 무슨 그림인가 싶겠지만...

브라우져가 화면을 렌더링하기 위해서는 복잡다단한 과정을 거친다.

이를 그나마 간략하게 표현했던 것이 [CSS] CSS Trigger에 이미지로 넣었던 Blink(크로미움의 렌더링 엔진)의 Rendering Pipeline이었는데, 그 세부과정을 조금 더 뜯어보면 위와 같은 절차가 포함되어있다. 렌더링하려는 HTML을 파싱해서 DOM Tree를 구성하고, DOM Tree와 CSSOM Tree를 하나로 합쳐서 Render Tree가 구성되고, 그 Render Tree를 바탕으로 화면에 표현될 Layer가 구성되게 되는데...!

 

이 포스팅에서 우리가 주의깊게 봐야 할 포인트가 바로 이 Layer다.

 

Layer라는 건 말그대로 '층'의 의미를 가진 형태인데, 이 '층'의 개념이 브라우져 렌더링시 도입되는 이유는 z축을 활용하는 3차원의 개념을 렌더링 과정에 삽입하기 위해서라고 이해하면된다. 좀 더 정확히 말하자면 Paint Layer는 Stacking Context를 구현하기 위한 적용방식이다.

 

Stacking Context. MDN에는 쌓임 맥락...이라는 오묘한 표현으로 번역이 되어있는데, HTML요소들을 사용자 기준으로 Z축을 세우기위해 적용된 것으로써, 가장 대표적인 사용법은 CSS의 z-index 값을 활용하여 화면을 구성하는 것이다.

 

아래와 같은 코드는

<body>
    <div style="position: absolute; width: 200px; height: 200px; left: 0;top: 0; background-color: crimson;">
    </div>
    <div style="position: absolute; width: 100px; height: 100px; left: 100px;top: 100px; background-color: cornflowerblue;">
    </div>
<div>

대략 이런형태로 브라우져에 렌더링되게 되는데, 사용자인 우리의 시선(Z축)으로 브라우져를 내려다보는 입장에서 보자면, 파란 사각형 뒤에 렌더링된 빨간 사각형의 일부 영역을 볼 수 없고 이와 같은 형태를 Stacking Context 라고해서 계층 구조를 띈다고 말하는 것이다.

 

그래서, Paint Layer는 Stacking Context를 위한 '층'으로 활용된다.

그리고 Paint Layer로 구성되는 '층'에서 GPU가 처리해야 하는 층이 있다면, Graphics Layer를 구성하게 되는 것이다.

 

3. Paint Layer 와 Graphics Layer가 되기 위한 Trigger

자, 그럼 다시 이 포스팅의 본론으로 돌아와보자.

숱한 글들에서 opacity, transform를 활용하면 reflow와 repaint 가 발생하지 않기때문에 브라우져의 불필요한 연산을 줄임으로써 최적화하는 방안으로 활용 할 수 있다, 라는 포인트가 유효해지는 이유는 뭘까.

 

뭐, 사실 눈치가 빠른 분이라면 이미 눈치를 챘을 것 같은데 Layer에 해당 요소가 적재되는 것이다.

 

Paint Layer는 기본적으로 1개로 구성되며 CPU가 렌더링에 주효한 역할을 하게 되어있다.

그리고 일반적으로 좌표 공간이 동일한 Layout Object는 동일한 Paint Layer에 적재되게 되어있는데, 우리는 아래와 같은 Trigger를 바탕으로 Paint Layer를 층층이 구성 할 수 있다(이 Trigger들은 사실 Stacking Context를 충족하는 Trigger이다)

1. 문서의 루트요소인 <html>
2. position이 absolute 또는 relative이고, z-index가 auto가 아닐때.
3. position이 fixed 또는 sticky인 요소
4. 플렉스(flexbox) 컨테이너의 자식 요소 중 z-index가 auto가 아닐때.
5. 그리드(grid) 컨테이너의 자식 요소 중 z-index가 auto가 아닐때.
6. opacity가 1보다 작을 때
7. mix-blend-mode가 normal이 아닐 때.
8. 다음 속성 중 하나라도 none이 아닐 때 : transform, filter, perspective, clip-path, mask
9. isolation이 isolate일 때.
10. -webkit-overflow-scrolling이 touch일 때.
11. will-change의 값으로 초기값이 아닐 때 새로운 Stacking Context를 생성하는 속성을 지정했을 경우
12. contain이 layout, paint 또는 둘 중 하나를 포함하는 값(strict, content등)인 경우
13. 화면에 보이지 않지만 층을 이룰 때

 

자...여기서 6번을 주목해보자. 

opacity가 1보다 작을 때, 라는 항목이 보인다.

우리는 opacity를 0.99로 줬을 때 reflow가 실제로 발생하지 않는 것을 보았다. 그리고 이제는 왜 reflow가 왜 발생하지않았는지를 알게되었다. 바로 opacity가 1미만의 값으로 설정되었기 때문에 해당 요소가 별도의 Paint Layer을 구성했기 때문이다.

 

Paint Layout의 Trigger가 위와 같다면, 이젠 Graphics Layer의 Trigger를 살펴보자.

Graphics Layer는 화면을 렌더링 할 때 GPU의 도움을 얻는 층이다.

GPU의 도움을 받으면 좋은 점은, CPU에 몰빵되어있던 렌더링 연산을 나눠서 함께한다는 것이다.

그리고 마치 땅따먹기를 하듯 GPU에 건내줄 일감을 선정하는 기준은 아래와 같다.

1. <video> 혹은 <canvas> 태그 사용
2. 3D transform 요소 적용
3. animation 이나 transition 사용
4. will-change로 opacity, transform, top, left, bottom, right등이 정의된 경우
5. iFrame일 경우
6. flash와 같은 일부 플러그인
7. backface-visibility 가 hidden일 경우

4. 그래서, 결론.

결국 CSS의 요소인 opacity나 transform을 단순 활용하는 것만으로는 우리가 기대하고 있던 '렌더링 연산을 줄임으로써 최적화'라는 목적은 달성 할 수가 없다. 화면의 렌더링 과정, 그리고 Layer 구성이 어떻게 되는지. 이 요소에 이 값을 줬을때 Paint Layer에 들어가는지, Graphics Layer에 들어가는지에 대한 명확한 확인이 필요하며...또한 무조건적으로  GPU의 도움을 받고자 Graphics Layer에 넣기위해 animation이나 transition을 쓰기보단, transition을 쓰더라도 reflow를 발생시키는 기하학적 요소들은 최대한 피하는 방안을 고려하는 등, 다각적인 입장에서 늘 설계하고 개발을 해야한다.


-1. 부록 : 그래서 repaint는 언제 발생하는거에요?

굳이 설명을 해야할까, 싶은 생각이 들었지만...이왕지사 시작한 거 추가적인 설명을 덫붙이기위해 부록을 추가한다. 나는 분명 위에서 이런 말을 했다.

opacity라는 스타일의 변경은 내가 함정에 빠졌던 윗 스크린샷과 같이 reflow부터 발생하기도하지만, 때로는 repaint부터 발생하기도하고, 또 때로는 reflow&repaint 없이 composite 단계에서 GPU의 도움을 받기도 한다.

reflow부터 발생하는 예제와 reflow&repaint가 발생하지 않는 예제를 본문에서 설명했다.

하지만 opacity를 변경하면서도 repaint가 발생하는 경우에 대해선 설명하지 않았는데...

이 상황을 억지로 발생시켜보기 위해 style 태그에 아래와 같은 코드를 적용했다.

<style>
  .app {
    display: flex;
    z-index: 1000;
  }
  .text{
    z-index: 2000;
    opacity: 1;
  }
  .opa{
    opacity: 0.3;
  }
</style>

repaint 발생!

그러자 repaint가 발생하는 걸 performance 탭을 통해 확인 할 수 있었다.

 

위 코드의 경우 reflow가 발생했던 코드에서 z-index와 display flex 요소가 추가되었는데, 이는 PaintLayer의 층 구분 조건 중 플렉스(flexbox) 컨테이너의 자식 요소 중 z-index가 auto가 아닐때를 충족한 경우라고 보면 된다. 그래서 실제로 opacity가 1에서 0.3으로 변경되는 코드임에도 불구하고 repaint가 발생하고 있는 것이다.

 


참고사이트

1. [Browser] 웹 브라우저 작동 원리

2. Front-End Performance Optimization(Alibabacloud) 

3. Chromium blink official ReadmeChromium compositing_reasons.h

댓글
최근에 올라온 글
Total
Today
Yesterday