Project/Pennypal

[FE] 스타일이 적용된 CSS 가상 요소 ::after가 GPU 사용률에 미치는 영향 - 원인 파악중,,

코릴라입니다 2024. 4. 8. 09:58

** 원인 파악중인 에러로 아직 해결하지 못했습니다,,,

 

🦙 WHY

- 프로젝트 진행 과정에서 특정 PC에서만 렌더링 오류가 발생하는 버그를 마주쳤습니다.
- 오류가 크게 발생하는 PC는 GPU 성능이 우수한 PC로 GPU 성능이 낮은 PC보다 훨씬 더 눈에 띄는 렌더링 오류가 발생했습니다.
 -> 성능 낮은 PC에서는 계속해서 윈도우 리사이징을 반복해야 GPU 사용률이 상승하는 정도이나 성능이 좋은 PC에서는 input에 텍스트를 입력할 때 화면 일부가 사라져 보이는 등의 심각한 에러가 발생했습니다.
- 여러 스타일이 적용된 가상 요소 ::after를 적용시킨 페이지에서 렌더링 오류가 발생했으며, 리플로우와 리페인트 과정에서 GPU 점유율이 크게 뛰는 원인을 파악하기 위해 여러 시도들을 진행했습니다.
 

🦓 WHAT

다른 상황에서의 GPU 사용률을 확인하기 위해 브라우저 윈도우의 리사이징을 의도적으로 반복했습니다.
여러 경우들을 나눠 실험해보았고, 그 결과로 각 상황에서의 GPU 사용률을 확인할 수 있었습니다.
** 문제가 발생했던 스타일 코드

        ::after {
            position: fixed;
            background-color: $black;
            z-index: -1;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            opacity: 0.05;
            content: '';
        }

1. ::after 적용

- 최대 GPU 점유율 : 69.6%
최대 GPU 사용률은 무려 94%까지 상승했습니다.
 

2. ::after 적용 해제

- 최대 GPU 점유율 : 6.4%
 
 

3. 스타일이 적용된 div 요소 생성, 제거 반복

여기까지 진행 후, ::after가 가상요소를 계속해서 생성, 삭제를 반복한다고 생각하여 윈도우가 리사이징될 때  div 요소의 생성, 제거를 반복하는 코드를 작성해 실험해보았습니다.

    const testRef = useRef<HTMLDivElement>(null);
    const [testc, setTestc] = useState(0);

    useEffect(() => {
        // useEffect: 윈도우 리사이즈 될 때 마다 state 변경시켜서 리렌더링
        function handleResize() {
            setTestc((prevTestc) => prevTestc + 1);
        }

        window.addEventListener('resize', handleResize);

        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []);

    useEffect(() => {
        // useEffect: state값 변경 감지해서 클래스명이 'test' 인 div 요소 생성
        const testDiv = document.createElement('div');
        testDiv.className = 'test';

        testRef.current!.appendChild(testDiv);

        return () => {
            // 생성된 div 제거
            testRef.current!.removeChild(testDiv);
        };
    }, [testc]);
        
// 스타일
.test {
    position: fixed;
    background-color: $black;
    z-index: -1;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0.05;
    content: '';
}

- 최대 GPU 점유율 : 13.0%
 
일반적인 경우 2배의 차이가 발생했습니다. 생성된 div 요소가 가진 클래스명인 'test'에 미리 CSS 속성들을 지정해 두었는데, 이를 동적으로 할당하는 방식으로 추가 진행해 보았습니다.
 

4. 생성된 div 요소에 동적으로 스타일 지정

   useEffect(() => {
        // useEffect: state값 변경 감지해서 클래스명이 'test' 인 div 요소 생성
        const testDiv = document.createElement('div');
        console.log(testc);
        testDiv.className = 'test';
        testDiv.style.position = 'fixed';
        testDiv.style.backgroundColor = 'black';
        testDiv.style.zIndex = '-1';
        testDiv.style.width = `100%`;
        testDiv.style.height = `100%`;
        testDiv.style.top = '0';
        testDiv.style.left = '0';
        testDiv.style.opacity = '0.05';
        testDiv.style.content = '';

        testRef.current!.appendChild(testDiv);

        return () => {
            // 생성된 div 제거
            testRef.current!.removeChild(testDiv);
        };
    }, [testc]);

- 최대 GPU 점유율 : 14.6%
 
스타일을 동적으로 지정했으나 GPU 점유율이 높아지지 않았습니다. div 요소의 생성, 삭제가 동시에 이뤄져 화면에 적용된 스타일이 나타나지 않는다고 판단해 코드를 수정해 다시 진행했습니다.
 

5. 생성된 div 요소의 삭제에 setTimeout 적용

    useEffect(() => {
        // useEffect: state값 변경 감지해서 클래스명이 'test' 인 div 요소 생성
        const testDiv = document.createElement('div');
        console.log(testc);
        testDiv.className = 'test';
        testDiv.style.position = 'fixed';
        testDiv.style.backgroundColor = 'black';
        testDiv.style.zIndex = '-1';
        testDiv.style.width = `100%`;
        testDiv.style.height = `100%`;
        testDiv.style.top = '0';
        testDiv.style.left = '0';
        testDiv.style.opacity = '0.05';
        testDiv.style.content = '';

        testRef.current!.appendChild(testDiv);

        return () => {
            // 생성된 div 제거
            setTimeout(() => {
                testRef.current!.removeChild(testDiv);
            }, 100);
        };
    }, [testc]);

- 최대 GPU 점유율 : 28.1%
 
시각적으로 효과가 적용된 것이 보이다보니 GPU 점유율이 상승한 것을 확인할 수 있었습니다만 ::after가 적용됐던 경우보다는 훨씬 낮은 점유율을 보이고 있습니다.
스타일이 적용된 HTML 요소의 생성, 삭제가 문제의 원인일 것이라 생각했었지만, 이제는 ::after의 동작 원리에 대해 궁금증을 품게 되었습니다.
 

6. ::after 가상 요소가 가진 스타일 속성 중에서 어떤 것이 문제를 일으켰나?

::after {
    position: fixed;
    background-color: $black;
    z-index: -1;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0.05;
    content: '';
}

다시 처음으로 돌아와 ::after 가상 요소에 적용된 스타일들을 하나씩 제외하며 실험을 진행했습니다.
그 결과 opcaity 속성이 적용되었을 때 문제가 발생함을 알게 되었습니다.
width, height 100% 속성을 통해 화면을 가득 채운 요소에 투명도 까지 적용되었을 때 GPU에 많은 부하가 걸린 것을 확인했습니다.
 
근데 왜 div 박스의 의도적인 생성, 삭제를 반복했을 경우에는 위 스타일이 적용되어 있음에도 불구하고 문제가 발생하지 않고, ::after 가상 요소에 한해서만 위와 같은 문제가 발생했을까요,,,,
 
요건 ::after, opacity 속성의 동작 원리 등에 대해 조금 더 찾아봐야 알 것 같습니다.
 
 

반응형