본문 바로가기

나만의 모음집

[성능최적화 모음집] 웹 성능을 최적화하기 위한 방법을 정리해서 모아둡니다.

반응형

이 포스트는..

해당 포스트는 제가 웹 개발 시 참고하기 위한 웹 성능 최적화 기법을 모아두는 아카이브로서 역할을 합니다. 한글 문서 등에 별도로 정리해두었던 내용들을 하나 씩 블로그에 옮기고 있습니다. 추가되는 내용이 있을 때 마다 최신 날짜를 기준으로 포스트가 갱신됩니다.

 

[이미지 최적화] 이미지 지연로딩을 통해 CPU 가 열일 하는 걸 줄여주자

보통 이미지 지연로딩을 사용하지 않고, 대량의 이미지를 로드하는 페이지를 렌더링하면, 무수히 많은 이미지를 렌더링 하기 위해 CPU 는 엄청난 과부하를 경험하게 됩니다. 브라우저의 경우에는 싱글 스레드로 동작하기 때문에 CPU 가 이미지 렌더링에 많은 시간을 소요하게 되면, 사용자 입장에서는 렉이 심하게 걸려서 마우스 조작도 힘든 버벅이는 화면을 오랫동안 볼 수 밖에 없게 됩니다.

 

이는 이윤을 쫓는 기업 입장에서는 고객 민원과 동시에 고객이 떠나가는 문제가 될 수도 있으니 심각한 문제 입니다.  특히 모바일 환경에서 경험한다면.. 끔찍하겠죠?..

 

따라서 이 문제를 개선하기 위해서 화면에 보이는 이미지만 우선 로딩하고, 나머지 이미지는 뷰포트에 보이는 순간에만 렌더링이 되도록 하여, CPU 의 부담을 줄여줄 필요가 있습니다. 

 

이미지 지연로딩을 구현하기 위한 방법 | img 태그의 속성 loading="lazy"

이미지 지연로딩을 구현하는 방법은 많이 있습니다. 최근에는 img 태그에 loading 속성에서 lazy 값을 지정하면 뷰포트 안에 이미지가 들어오면 이미지를 로드하는 최적화 속성이 존재합니다. 

 

바로 다음과 같이 지정만 하면, 별도의 노력없이도 지연로딩이 가능합니다. 이렇게만 해둬도 내부적으로 알아서 뷰포트에 이미지의 레이아웃이 들어오는 순간 이미지를 로드하기 시작합니다.

<img src="image.jpg" loading="lazy" alt="Lazy Loaded Image"/>

 

현재 2024년 4월 3일 기준으로 찾아본 결과 다음과 같이 모든 브라우저에서 지원이 되는 것을 볼 수 있는데요. 그럼에도 과거 버전을 사용하는 사용자가 있을 수 있기 때문에, 해당 속성을 적용하지 못하는 사용자의 경험까지 생각한다면 추가적인 조치가 필요해 보입니다.

 

혹시나 해서 NextJS 에서 사용되는 Image 컴포넌트 내부의 소스 코드를 살펴보았는데, 다양한 최적화 기법 중 loading 속성이 img 태그 안에 부여된 것을 볼 수 있었습니다. 물론 다양한 복잡한 성능 개선을 위한 로직이 들어가 있겠지만, 알고 가는 것과 모르고 있는 것은 차이가 크기에 언급해보고 갑니다.

 

이미지 지연로딩을 구현하기 위한 방법 | IntersectionObserver 를 이용한 지연로딩

인터섹션 이전에 스크롤 이벤트가 있죠.

사실 해당 메서드 말고도 window 에 스크롤 이벤트를 등록해서 이미지의 높이를 측정하여 일정 스크롤 기점에 이미지가 로드되도록 하는 방법도 있지만 이 방법은 권장되지 않습니다. 스크롤 이벤트가 등록되는 것만으로도 메모리 공간을 차지하는 행동인데, 이러한 이벤트가 스크롤 때 마다 계속 등록이 된다면.. 메모리의 공간은 좁아 터질지도 모릅니다. 

 

또한, 자바스크립트 코드를 실행하는 것 자체가 CPU의 입장에서는 처리해야 할 연산 그 자체이므로, CPU 는 자바스크립트 코드가 실행되는 중에 해당 코드가 실행됨으로써 발생하는 명령과 연산에 대한 계산 처리, 데이터 저장 및 불어오기 등의 제어 작업 등등 다방면의 일을 수행하게 됩니다.

 

그런데,, 이렇게 바쁜 CPU 라는 친구에게 일감을 몰아주는 녀석이 스크롤 이벤트 입니다. 스크롤이 될 때 마다 스택 오버플로우를 연산시키는 과도한 이벤트와 함께 호출되는 무수한 함수들의 연쇄 호출은 생각만 해도 CPU 에겐 끔찍한 악몽이 아닐까 나름 감정이입을 해봅니다.

 

그러니까, 스크롤 이벤트 말고 인터섹션옵저버를 이용해 

IntersectionObserver 를 사용하면 뷰포트에 들어오는 요소들을 대상으로 특정 동작을 수행하도록 컴퓨터에게 지시할 수 있습니다. 즉, 스크롤 이벤트와 다르게 요소가 특정 지점에 들어오는 위치에서 메소드가 호출되도록 하기 때문에, CPU가 가지는 부담을 크게 줄여줄 수 있습니다.

 

무엇보다 사용하는 입장에서도 간단하게 코드 몇 줄만 짜두면 알아서 뷰포트와 요소 간의 거리를 감지하고 계산해서 들어왔니? 나갔니? 를 감시해주니 이보다 고마운 친구가 있을까 싶네요

 

구현하는 방법은 사실 정말 간단합니다. 복잡한 연산은 이 친구 내부적으로 처리하고 있기 때문에 저희는 그냥 사용법에 따라 가져다 조금만 바꿔주기만 하면 됩니다.

// Intersection Observer 생성
let observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 화면에 진입한 경우 이미지 로드
      const img = entry.target;
      const src = img.getAttribute('data-src');
      img.setAttribute('src', src);
      // 관찰 중지
      observer.unobserve(img);
    }
  });
});

// 이미지 요소들을 모두 가져와 Intersection Observer에 관찰
document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

 

아래 코드를 보면 img 태그의 속성 중 data-src 를 가지고 있는  img 요소를 모두 선택해서 forEach 로 반복을 돌리고 있습니다. 그리고 각  img 요소에 observe  메소드를 적용하여 IntersectionObserver 가 관찰하는 대상으로 지정합니다. 즉, 이제 img 태그들은 어딜 가든 감시받고 살아야 하는 처지가 된 것이죠.

 

 

그 다음, 각 img 친구들이 뷰포트에 들어왔는지 안 왔는지 확인하기 위해 isIntersecting 이라는 프로퍼티를 조회하는데, 타겟이 되는 entry 요소가 뷰포트에 들어오면 true, 아니면 false 를 반환해줍니다. 이를 활용해서 앞서 img 태그의 속성으로 숨겨두었던 data-src 속성을 getAttribute 로 읽어와서 src 변수에 담은 후 이를 다시 img 태그의 src 에 담아주기만 하면 그 때부터 브라우저는 src  에 연결된 자원을 추적하여 렌더링하기 시작합니다.

 

이게 끝입니다.  마지막으로 observer 의 관찰 대상으로 img 를 제거해줌으로써 불필요한 메모리 낭비를 줄이는 것 까지 해서 모든 작업이 마무리 되었습니다. 

 

 

다만 이 친구도 브라우저 호환성을 고려할 필요는 있습니다. 현재는 필요한 모든 옵션의 경우에는 모든 브라우저에서 지원해주고 있긴 합니다.

https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API#%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80_%ED%98%B8%ED%99%98%EC%84%B1

 

[이미지 최적화] 이미지 크기를 지정하는 것은 CPU 가 할 일을 줄여줍니다.

저는 어떤 최적화 기법을 생각하더라도 최종적으로 CPU 에 부담이 되지 않을까? 를 우선 생각합니다. 제가 사용하는 컴퓨터가 똥컴이다 보니 조금의 과부하만 걸려도 버벅이는 렉을 경험하고 있는데요. 그러다 보니 CPU의 눈치를 매번 보고 있습니다.

 

하지만 제 컴퓨터보다 더 똥컴이 있습니다. 그건 휴대폰이죠. 휴대폰은 가히 작은 컴퓨터로 불려도 무방할 정도의 성능을 자랑합니다. 그럼에도 데스크톱 보다는 성능이 매우 딸리기 때문에, 웹 페이지를 모바일에서 실행시키는 경우 많은 렉이 발생하는 것을 볼 수 있습니다. 그 중에서 레이아웃 시프트도 자랑스럽게 한 몫 하고 있죠.

 

이런 상황에서 이미지 크기를 지정하는 것 만으로도 어떤 이점이 있을 지 우선 언급하고 지나가겠습니다.

 

이미지 크기만 지정해줘도 이런 이점들이?!!

페이지 렌더링 속도 향상

우선, 브라우저가 이미지를 로드할 때 width와 height를 지정하면 브라우저가 렌더링을 시작할 때 이미지의 크기를 미리 알 수 있습니다. 따라서 브라우저는 레이아웃을 계산하고 이미지가 로드될 때까지 대기하지 않고 페이지를 빠르게 렌더링할 수 있습니다.

 

레이아웃 안정성 향상

두 번째로, width와 height를 지정하면 이미지가 로드되기 전에 브라우저 렌더링 엔진이 해당 이미지의 공간을 미리 확보할 수 있습니다. 이렇게 하면 페이지의 레이아웃이 변경되거나 재배치되는 것을 방지하여 사용자 경험을 향상시킬 수 있습니다. 즉, 레이아웃 시프트 현상을 감소시킬 수 있는 것이죠.

 

이미지 재사용성

이미지의 크기를 명시적으로 지정하면 동일한 이미지를 여러 번 사용할 때 일관된 크기를 유지할 수 있습니다. 이는 웹 페이지의 일관성을 유지하고 디자인을 개선하는 데 도움이 됩니다.

 

이 점은 보통 대량의 동일한 사이즈의 이미지를 렌더링할 때 이점이 큰 부분인데요. 보통 대량의 이미지를 각각 태그를 생성해서 만들기 보다는 반복문을 사용하잖아요? 이럴 때 이미지의 크기를 지정해두면, 일관된 크기로 렌더링할 수 있으니 재사용성을 높이는 것도 이점 중 하나입니다. 좀 당연한 소리이긴 하죠.

 

이미지 로딩 시각화

이 부분은 레이아웃의 안정성 향상의 연장선 부분인데요, 브라우저에서는 이미지가 로드되기 전에 해당 이미지의 크기를 미리 계산하여 로드되는 이미지를 시각적으로 나타낼 수 있기 때문에,  width와 height를 지정하면 브라우저가 이미지의 크기를 알고 있으므로 로딩 중에 레이아웃이 변하는 것을 방지할 수 있습니다.

 

또한, 대체 이미지라고하죠? 본래의 이미지가 로드되기 전에 페이크 이미지를 사전 렌더링하게 두면, 사용자 입장에서도 아 여기에 이미지가 존재하는 구나 라는 것을 인지할 수 있기 때문에, 이미지 로딩을 시각화할 수 있다는 장점도 있습니다.

 

그러니 이미지의 크기를 지정 해주자

앞서 언급한 말의 연장이지만, 다시 언급하자면 이미지의 크기가 명확하지 않으면 브라우저는 해당 이미지를 로드할 때 초기에 로드 후 다시 한 번 변경된 레이아웃의 위치를 계산하기 위해 리플로우 작업을 수행하게 됩니다.

 

즉, 했던 일을 한 번더 하는 웃픈 상황이 발생하는 것이죠.

 

따라서 해당 문제를 방지하기 위해서 img 태그의 자체 속성인 width 와 height 를 지정하여 브라우저의 할 일을 줄여주도록 합시다.

    <!-- 이미지에 width와 height 속성 적용 -->
    <img src="example.jpg" alt="Example Image" width="300" height="200">

 

앞서 서두가 길었던 거에 비해서 최적화를 위한 조치는 별거 없습니다. 그냥 명시적으로 width 와 height을 지정해두면 끝이니까요.

 

다만, 이대로만 설정한다면, 이미지가 필요로 하는 크기를 정확히 알고 있는 상황에서는 문제가 될 것은 없으나, 동적으로 이미지의 크기가 변경되어야 하는 상황에서는 사용하기 어려울 수도 있습니다.

 

따라서 이 문제를 개선하기 위한 추가적인 CSS 속성으로 aspect-ratio 라는 속성을 지정하여 종횡비를 지정해두면 보다 유연한 레이아웃 설계를 할 수 있습니다.

 

aspect-ratio 를 통해 종횡비를 지정하면, 보다 유연한 이미지 렌더링이 가능합니다.

보통 이미지가 렌더링된 요소를 개발자 도구에서 마우스 호버해보면 해당 이미지에 적용된 종회비를 알 수 있습니다. 이 종횡비는 매우 중요한 요소로 브라우저가 해당 이미지가 어떻게 확장되는지를 인지하는 토대가 됩니다. 즉, 해당 이미지의 레이아웃이 어떤 방향으로 지정될 것인가를 알 수 있도록 해줌으로써 이미지의 크기가 동적으로 확장되어도 이 비율은 벗어나지 않을 것이라 알 수 있으므로 어떻게 보면 레이아웃 시프트 문제를 해결하는 데에도 도움을 줍니다.

 

그럼 예시를 살펴보도록 하죠. 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Aspect Ratio Example</title>
    <style>
        .square-image {
            width: 200px; /* 이미지의 가로 길이 지정 */
            aspect-ratio: 1/1; /* 이미지의 가로-세로 비율을 1:1로 지정하여 정사각형 모양으로 설정 */
            overflow: hidden; /* 가로-세로 비율을 유지하는 이미지가 넘치는 경우를 대비하여 오버플로우 처리 */
        }
        .square-image img {
            width: 100%; /* 이미지가 부모 요소의 크기에 맞게 되도록 설정 */
            height: auto; /* 이미지의 세로 길이를 자동으로 조정하여 비율 유지 */
        }
    </style>
</head>
<body>
    <div class="square-image">
        <img src="example.jpg" alt="Square Image">
    </div>
</body>
</html>

 

아래 예시를 보시면 .square-image 클래스가 적용된 div 태그에 width 는 200px , aspect-ratio 는 1/1 로 지정한 것을 볼 수 있습니다. 이렇게 되면 브라우저는 해당 요소를 렌더링할 때 넓이 200px 을 기준으로 높이를 1 대 1 비율로 맞추게 됩니다. 즉, 200 x 200 으로 태그의 크기를 지정하는 것이죠.

 

그리고 해당 태그의 내부에는 실제 img 요소가 들어가 있는데요. 보통 자식 요소는 부모요소의 레이어드에 속하기 때문에 width와 height를 아래와 같이 지정하게 되면, 부모 영역에 맞춰서 크기를 조정하게 됩니다.

 

 

이렇게만 해둔다면, 브라우저의 입장에서는 해당 이미지가 어떻게 렌더링 될 것인지 알 수 있기 때문에, 결과적으로 레이아웃 시프트나 부드러운 렌더링에 있어서 큰 이점을 가져갈 수 있게 됩니다.

 

[이미지 최적화] 최근에는 css 속성 하나로 렌더링 시점을 정할 수 있습니다. 

이거는 제가 최근에 알게된 것인데요. CSS 에서 content-visibility 라는 속성을 사용하면 img 가 브라우저 화면에 렌더링 되는 시점을 설정할 수 있습니다. 즉, 초기 렌더링 시 불필요한 이미지를 모두 렌더링하지 않도록 설정할 수 있는 것이죠.

 

이게 원래라면은 intersectionObserver 라는 던지, 스크롤 이벤트라던지 등을 사용하여 렌더링 시점을 계산하여 지정해줘야 했는데요. 이 CSS 속성을 사용하면 그럴 필요? 없습니다. 정말 대단한 기능이죠.

 

아래는 MDN 에 있는 content-visibility 의 개념 중 일부인데요. 파란 줄을 보시면 요소가 그 내용을 렌더링할지 여부를 통제하는 속성이라고 나와 있습니다. 이것만 봐도 해당 속성이 어떤 역할을 하는지 느낌 오죠.

 

그럼 어떻게 사용하느냐? 이에 대해서 정리해볼까 합니다.

 

content-visibility 와 auto를 합치면

다양한 값들이 있긴하지만, 이미지를 최적화하기 위한 용도로 사용되는 값은 auto 입니다. 이 값으로 해당 속성을 지정하면,  사용자가 해당 콘텐츠의 가까이에서 스크롤하는 동안 자동으로 렌더링 시점을 결정해주기 때문에, 개발자는 별도의 조작 필요없이 이미지를 찍어내기만 하면 됩니다.

  img {
    content-visibility: auto;
  }

 

 

다만 해당 속성을 지정하는 이미지는 초기 뷰포트에 바로 보이는 경우라면 굳이 사용할 필요가 없습니다. 처음 등장하는 이미지는 우선 렌더링되어야 하는 것이 좋은데, 이 때 해당 속성을 지정하는 경우 사용자가 스크롤 등의 동작을 수행하지 않는다면 렌더링이 이루어지지 않을 가능성이 높기 때문입니다.

 

무엇보다 아직 해당 속성은 시험 버전이라는 사실..

크로스 브라우징 문제를 생각한다면, 아직 까지는 실무에서 사용하기에는 제한 상황이 많아 보이네요. 그래도 알아두면 좋은 속성임은 분명해 보입니다.

 

참고자료

- content-visibitiy: https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility#auto

반응형