티스토리 뷰
본론에 들어가기 앞서, 현대의 웹 개발에서 렌더링 최적화가 모바일 앱에 비해 상대적으로 중요하지 않게 느껴질 수 있습니다. 이는 모바일 앱과 웹 간의 명확한 차이점 때문에 그럴 수밖에 없습니다.
모바일 앱은 성능과 네이티브 기능 구현에 중점을 두며, 이러한 최적화에 대한 제약이 상대적으로 적습니다. 예를 들어, 플러터는 자체 렌더링 엔진인 스키아(Skia)와 임펠러(Impeller)를 사용하며, 웹과는 다른 레이아웃 계산 방식을 채택하고 있습니다. 객체 지향적으로 잘 설계되어 있어 커스터마이징이 용이하고, 자체적으로 최적화하기도 어렵지 않습니다. 다만, 이러한 장점에도 불구하고 최적화가 부족한 앱들이 간혹 존재하기도 합니다.
반면, 웹은 렌더링 성능보다는 주로 접근성에 중점을 두기 때문에, 개발자들은 웹의 렌더링 성능보다는 접근성 향상에 더 많은 노력을 기울일 수밖에 없습니다. 또한, 웹 개발에서는 종종 개발 기간이 촉박할 수 있어 이러한 경향이 더욱 두드러질 수 있습니다.
본론으로 넘어가면, 웹의 렌더링 성능에 영향을 미치는 주요 원인은 바로 Reflow입니다. Reflow는 브라우저가 특정 요소의 렌더링 과정에서 발생하는 중요한 단계 중 하나입니다.
브라우저는 DOM 요소의 스타일이나 크기 변화가 발생하면, 다음 프레임까지 기다려서 한꺼번에 레이아웃을 계산하는 것이 아니라, 비동기적으로 작업을 수행합니다.
<div style="display: flex;">
<div class="a">Hello, World!</div>
<div class="b" style="width: 100%; background-color: red">Hello, World!</div>
</div>
예를 들어서, 다음과 같은 요소에서 div.a 요소가 변화하면 div.b 요소도 같이 reflow 과정을 거쳐야 한다는 것을 어림잡아 쉽게 알 수 있습니다.
여기서 div.a 요소의 크기가 업데이트될 때 div.b 요소가 reflow된다. 라는 말은 div.a 요소가 div.b 요소의 렌더링 비용이 크다면 같이 div.a 요소의 reflow도 같이 지연된다는 것을 의미합니다.
이렇게 되면 div.a 요소의 애니메이션이 div.b 요소의 영향으로 끊길 수 밖에 없습니다. 이러한 현상을 방지하기 위해서는 당연하게도 div.b의 reflow 비용을 최소화할 수 밖에 없습니다. 지금부터 제가 겪었던 문제와 해결방안에 대해 자세히 설명해드리도록 하겠습니다.
해당 상황에 해당하는 예시 동영상
overflow: hidden
overflow의 hidden 속성 값은 CSS에서 매우 널리 사용되는 속성 중 하나입니다. 이 속성은 부모 요소의 크기를 넘는 자식 요소의 내용을 화면에 표시하지 않도록 하는 역할을 합니다. 즉, 자식 요소가 부모 요소의 경계를 넘어가는 부분을 숨기고, 보이지 않게 처리합니다.
그러나 overflow: hidden을 사용할 때에는 성능적인 측면을 고려해야 합니다. 이 속성을 적용하면 브라우저가 추가적인 계산을 수행해야 하는데, 그 이유는 부모 요소가 자식 요소의 크기 변화를 지속적으로 감지하고, 그에 따라 넘치는 부분을 처리해야 하기 때문입니다. 특히, 자식 요소의 크기나 위치가 빈번하게 변하지 않는 경우에도 매번 해당 연산이 발생하게 되어 reflow 비용이 크게 증가할 수 있습니다.
더 나아가, 만약 부모 요소에 border-radius와 같은 속성이 적용되어 있다면 상황은 더욱 복잡해집니다. 이 경우, 브라우저는 자식 요소가 부모의 경계뿐만 아니라 둥근 모서리 내부로도 들어맞도록 계산을 해야 하기 때문에 reflow 비용이 더 많이 발생할 수 있습니다.
따라서 overflow: hidden을 사용할 때는 꼭 필요한 상황에서만 사용하는 것이 좋습니다. 특히, 성능에 민감한데 반해 많은 요소가 동적으로 변화하는 페이지에서는 과도한 사용을 피하는 것이 좋습니다. 물론, 일반적인 상황에서는 reflow가 빈번하게 발생하지 않기 때문에 체감이 크지 않을 수 있지만, 대규모 프로젝트나 복잡한 UI 또는 기타 등등의 상황에서 성능 최적화를 고려할 때는 이 속성의 사용을 신중하게 검토해야 합니다.
결론적으로, overflow: hidden은 매우 유용한 속성이지만 성능 최적화를 위해 적절히 사용해야 하며, 불필요한 경우에는 가능한 사용을 자제하는 것이 좋을 것같습니다.
응용 라이브러리:
(터치 효과가 보이지 않을 때에도 필수적으로 초기에 자식에게 overflow: hidden 속성 값을 적용하여 성능이 하락한 경우였습니다, 결론적으로 앞서 설명했던 이슈를 해결한 라이브러리 중 하나입니다.)
border-radius
border-radius 속성은 CSS에서 요소의 모서리를 둥글게 만들어 주는 매우 중요한 디자인 기능입니다. 이 속성은 요소의 경계선에 적용하여 박스 모양의 딱딱한 모습의 요소를 부드러운 곡선으로 처리함으로써 디자인적으로 더 세련된 느낌을 만들어 줄 수 있습니다. 특히 현대적인 웹 디자인에서는 매우 많이 자주 사용되며, 사용자 경험을 향상시키는 데에도 당연한 도움을 줍니다.
다만 제가 알고 있는 드 카스텔조 알고리즘만을 봐도 재귀적으로 추정치를 구하거나 기타 다른 방법으로 값을 부드럽게 보간시키는데 이러한 과정을 1초에 60~240번 반복하고 그릴려면 연산 비용이 높아질 수 밖에 없을 것입니다.
이러한 사실을 근거로 하여 백터 이미지의 경우도 일반적인 이미지보다 훨씬 많은 렌더링 비용이 들어가며 SVG의 과도한 남용은 애니메이션 효과에 매우 매우 큰 타격을 입힙니다. (다수의 SVG 라는 가정하에)
하지만, border-radius의 효과를 대체할 수 있는 직접적인 CSS 속성은 현재로서는 존재하지 않습니다. 그만큼 이 속성은 독특한 기능을 제공하며, 웹 디자인에서 필수적인 역할을 하는 일종의 고유한 핵심 기능이기 때문입니다. 그러나 성능 최적화가 필요한 경우, border-radius의 과도한 사용을 피하거나, 필요에 따라 앞서 설명할 성능 최적화 기법을 한번 적용하는 것도 중요합니다.
보일 때만 렌더링하기
해당 기법은 성능 최적화의 대표적이고 원시적이면서 가장 효과적인 방법 중 하나로, 보이지 않는 요소에 대해 불필요한 연산을 생략함으로써 렌더링 성능을 향상시키는 데 중점을 둡니다. 당연하게도 기본적으로 보이지 않는 요소는 화면에 렌더링될 필요가 없기 때문에, 레이아웃을 계산하거나 스타일을 적용하는 과정을 줄일 수 있습니다.
하지만 이 기법을 구현하고 적용하는 것은 생각만큼 간단하지 않을 수 있습니다. 요소가 보일 때와 보이지 않을 때를 정확하게 구분해야 하고, 적절하게 렌더링 과정을 제어하는 데는 추가적인 코드나 복잡한 로직이 필요합니다. 보이지 않는 요소를 감지하고, 그에 따라 렌더링 여부를 결정하는 로직을 작성하는 과정에서 성능이 좋아질 것이라는 보장도 없습니다. 즉 모든 웹들이 이 방식에서 동일한 성능 향상을 얻는 것은 아니기 때문입니다. 상황에 따라서는 오히려 성능이 악화될 수도 있고 웹의 비동기적인 reflow 특성으로 사용자는 뒤늦게 요소를 볼 수 있게 하는 주요 원인이 되기도 합니다.
그러나 div.a 요소의 크기에 따라 div.b 요소의 크기가 결정되며, div.b의 reflow 비용이 상당히 크다면 상황이 달라집니다. 만약 div.b의 reflow 비용이 매우 큰 상태에서는, 앞서 설명했던 것 처럼 div.a의 성능이 함께 저하되는 문제를 겪을 수 있습니다. 이 경우엔 div.b 내부의 아이템들이 보일 때만 렌더링되도록 처리하는 방법을 적용함으로써 애니메이션 성능을 크게 개선시킬 수 있습니다.
실제로 이와 같은 최적화 방법을 통해 성능이 크게 향상된 저의 사례도 존재합니다. 예를 들어, 초기에는 평균적으로 50fps를 유지하던 페이지가 이러한 최적화를 적용한 후 평균 120fps 이상까지 성능이 증가하였습니다. 이는 약 3배에 가까운 성능 향상을 의미하며, 특정 상황에서 매우 유용한 방법으로 적용될 수 있음을 명확하게 보여주는 결과입니다. 특히, 요소 간의 레이아웃 의존성이 복잡하게 얽혀 있을 때 이러한 최적화가 더욱 빛을 발할 수 있겠죠.
다만 명확한 단점은 상황에 따라 요소가 보이는지 안보이는 지를 구분해야 하는 로직에 개발자가 애를 먹을 가능성이 있으며 웹의 특성상 스크롤 도중 요소들이 reflow되면 렌더링이 지연되고 오히려 렌더링 성능이 하락할 수 있습니다. (플러터의 경우, 초기 근사치를 바탕으로 스크롤이 가능한지 여부만을 기준으로 동작합니다. 따라서 아이템들이 모두 렌더링 과정을 거치고 나서야 비로소 정확한 최대 스크롤 값을 얻을 수 있습니다, 또한 Jetpack Compose의 경우는 최대 스크롤 값을 기본적으로 제공해주지 않습니다. Android 네이티브도 마찬가지로 초기 근사치를 이용합니다.)
따라서 보일 때만 렌더링하는 이러한 방법은 일반적인 성능 개선책으로 고려해 볼 만하며, 특히 성능에 민감한 UI나 다수의 동적 요소가 존재하는 웹 애플리케이션에서 매우 효과적일 수 있습니다. 하지만 무조건적인 성능 향상을 기대하기보다는 상황에 맞는 분석과 적용이 필요합니다.
관련 React 라이브러리
(제 패키지입니다, 앞서 설명한 기법을 쉽게 구현하게 하는 Invisible 위젯을 참고해보세요.)
응용 웹 사이트
참고로 이러한 최적화 기법은 흔히 모바일 앱 개발에서는 스크롤 성능과 초기 렌더링 지연을 해결하기 위해서 개발된 Android의 Recyclerview 라던가 플러터의 ListView.builder 위젯이 있겠습니다. (근데 애초부터 일반적인 웹의 UI 구조와 특성에 의해 이러한 최적화 기법은 거의 필수적으로 사용되는 모바일 앱 개발과는 다르게 잘 사용하지 않습니다.)
제 글을 읽어주셔서 감사합니다. 🫡
'웹 (Web)' 카테고리의 다른 글
[React] 이전 페이지의 상태를 유지하는 경량 리액트 라우터 (2) | 2024.10.09 |
---|---|
[Typescript] Omit과 관련된 인덱스 서명(Index Signature) 문제 (1) | 2024.09.23 |
[Web API] 특정 요소의 고유한 크기를 참조하는 방법 (스케일 영향 X) (0) | 2024.08.14 |
[TS] 타입스크립트에서 여러가지 유용한 기능들 2 (0) | 2024.07.02 |
[Web API] Reflow를 유발하여 레이아웃을 제어하는 방법 (0) | 2024.06.19 |
- Total
- Today
- Yesterday
- JavaScript
- 팩토리 메서드
- Flutter
- 객체 지향
- svg
- flutter_touch_ripple
- 최적화
- webpack
- 터치 효과
- github
- 안드로이드 개발
- 안드로이드
- 객체지향
- web
- 터치 리플
- Reflow
- 깃허브
- 타입스크립트
- Factory Method
- TypeScript
- omit
- html-inline-webpack-plugin
- android
- 전환 애니메이션
- 조건부 타입
- mangler
- 리플 효과
- 플러터
- jetpack compose
- 디자인 패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |