프론트엔드

Lazy Loading

2022. 9. 22. 21:40
레이지 로딩은 이름부터 알기 쉽습니다.
이미지 로딩을 웹을 띄우고 나서 천천히 하는 것과 사용자의 데이터 낭비를 막아주는 것. 
레이지 로딩에 대해서 알아보겠습니다. 

 

Lazy Loading 이란? 

 

 

레이지 로딩은 이미지에 대한 로딩을 뒤로 미루는 것을 의미합니다. 

필요한 부분만 불러오고 나머지 부분은 사용자가 필요할 때까지 미루는 것으로 사용자의 데이터 낭비를 막아줍니다. 

 

기존의 페이지는 웹 페이지를 열면 브라우저가 모든 이미지를 읽고 불러와서 DOM에 렌더링할 것입니다. 

이미지가 많다면 여는 시간도 오래 걸릴 것이고 데이터도 많이 들 것입니다. 

 

예를 들어 사용자가 검색 엔진에서 한 이미지를 검색하면, 요청한 내용으로 채워진 전체 웹 페이지가 로드됩니다.

이미지가 많기 때문에 전체를 렌더링 하기 위해서는 데이터 양이 많을 것입니다. 하지만 우리가 필요한 이미지는 사실 맨 위의 다섯장 정도 안에 있기 마련입니다. 이런식으로 필요없는 이미지를 불러오게 되면 리소스가 낭비됩니다. 

 

lazy Loading을 사용하면 placeholder 콘텐츠로 작성되며 사용자가 필요할 때만 실제 콘텐츠로 대체됩니다. 

무한 스크롤에서, 아래로 스크롤해서 내리는 애니메이션을 통해  웹 페이지의 내용이 로드되는게 하나의 예입니다. 

 

 

 

Lazy Loading 장점

시간 소비와 메모리 사용량을 줄여 콘텐츠 전달을 최적화할 수 있습니다. 

필요한 웹페이지의 일부가 먼저 로드되기 때문에 시간이 적게 소요되고, 나머지의 로딩이 미뤄져 저장공간이 절약됩니다.

불필요한 코드 실행을 피하고 시간과 공간 자원의 최적화된 사용을 가능하게 합니다. 

 

Lazy Loading 단점

코드가 복잡해질 수 있고, 로드되지 않은 컨텐츠의 부적절한 인덱싱으로 검색 엔진에서 순위에 영향을 미칠수 있습니다.

 

 

 

Lazy Loading 예시

사진이나 영상에 사용하거나 사용자에게 바로 보여지는 페이지에서 이미지를 나중에 불러오는 방법이 있습니다. 

 


Lazy loading 구현을해보겠습니다.

 

방법1 

이미지 로드를 사전에 막고 브라우저에게 해당 이미지를 언제 로드할건지에 대한 것을 알려주어야 합니다. 

이번 방법은 IntersectionObserver API 를 사용해서 쉽게 구현해 보겠습니다.

(실제로 스크롤 이벤트리스터보다 성능도 좋다고 합니다. )

 

Lazy Load 시킨 이미지들을 로딩시키기 위해 해당 이미지들에 옵저버를 적용합니다. 

엘리먼트가 뷰포트에 들어간 것을 확인하기 위해 isIntersecting, intersectionRatio 속성을 사용합니다.

또한 확인이 되면 url을 data-src속성에서 src 속성으로 브라우저가 이미지를 로드하도록 트리거를 일으킵니다. 

그 후 해당 이미지는 lazy 클래스를 제거하고 옵저버를 해제합니다. 

 

const useLazyLoading = () => {
  const imgs = document.querySelectorAll('.lazy');

  const observerCallback = (entries, observer) => {
    entries.forEach(({ isIntersecting, intersectionRatio, target }) => {
      if (isIntersecting && intersectionRatio > 0) {
        target.src = target.dataset.src;
        target.classList.remove("lazy");
        observer.unobserve(target);
      }
    });
  };

  const Intersection = new IntersectionObserver(observerCallback);
  imgs.forEach((img) => Intersection.observe(img));
};

useLazyLoading();
<img class="lazy" data-src="src~" />

 

<img src="src~" />​

 

변경된 모습 (전,후)

 

 

구현을 해서 빨리 스크롤을 내려보면 아직 불러오지 못한 이미지는 배경이 노란색인 것을 확인할 수 있습니다.

 

 

Reference

https://mari-mo.tistory.com/206 , https://onlydev.tistory.com/104

'프론트엔드' 카테고리의 다른 글

프론트엔드 성능 최적화  (1) 2022.09.08
브라우저 동작 원리  (0) 2022.09.08
👉 성능은 사용자 유지를 좌우합니다

핀터레스트는 인지된 대기 시간을 40% 줄여 검색 엔진 트래픽과 가입수를 15% 늘렸습니다.

COOK은 평균 페이지 로드 시간을 850 밀리초로 줄여 전환율을 늘리고, 세션당 페이지 수를 10% 늘렸습니다. 

연구에 따르면 사이트를 로드하는 시간이 1초 추가될 때마다 사용자 수가 10% 줄어든다는 사실을 발견했습니다.

 

이처럼 성능은 사용자 경험을 좌우하기 때문에 기본적으로 좋아야합니다.

많은 양의 코드를 제공하면, 데이터가 많기 때문에 최적화되지 않았다고 판단하기도 합니다. 

 

프론트엔드에서 성능을 최적화 하려고 하면 어떤 방법이 있을까요?

 

https://coffeeandcakeandnewjeong.tistory.com/34 의 글을 보고 공부하고자 올렸습니다.

페이지 로드 최적화 

1. 블록 차단 리소스 최적화 

 

HTML을 파싱할 때, CSS나  JS를 만나게 되면, HTML 파싱을 중단하고 해당 파일을 파싱하거나 다운로드 후 실행하게 되는데,

이런 파싱 차단 요소를 블록 차단 리소스라고 합니다.

👀  HTML 파서는 스크립트 태그를 만나면 DOM 생성 프로세스를 중지하고 JS 엔진에 제어 권한을 넘깁니다. 
JS  엔진의 실행이 완료된 후 브라우저가 중지했던 시점부터 DOM 생성을 재개합니다.
인라인 스크립트를 실행하게 되면 DOM 생성이 차단되고, 초기 렌더링이 지연되게 됩니다.

 

블록 차단 리소스는 올바른 실행 위치에서 코드를 작성해야 합니다.

CSS 의 경우 <head> 태그 안에서 불러와야 합니다. <script> 는 <body> 맨 하단에 위치시켜야 합니다.  

<head>
  <link href="./css/style.css" rel="stylesheet"/>
</head>
<body>
  ...
  <script>
  </script>
</body>

CSS 의 경우는 media attribute 로 어떤 단말기의 유형인지에 따라 해당 css를 적용할지 명시하면 불필요한 블로킹을 방지할 수 있습니다.

<link href="mobile.css" rel="stylesheet" media="width:780px" />

JS 의 경우 비동기로 다운로드 하도록 명시하면 , DOM 트리나 Sytle 트리를 변경하지 않겠다는 의미이므로 async,defer.. 을 활용합니다.

<script async> html 파싱을 블로킹하지 않고 다운로드

 

preload와 prefetch 방식으로 css 파일의 로드 방식을 변경하는 방법도 있습니다.

<link href="style.css" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'"/>

preload 와 as=style 로  load 이벤트를 막지 않으면서 css 파일을 요청할 수 있습니다.

onload='this.onload=null'과 'this.rel='stylesheet' 로 css 파일이 로드 이벤트 이후에 파싱되고 onload 함수가 제거됩니다.

 

 

실제로 웹 폰트를 preloading 하면서 성능을 향상시킨 사례들이 있습니다.

 

👀 preload vs prefetch 언제, 무엇을 사용해야 할까?
중요한 컨텐츠를 우선적으로 요청해 먼저 보여져야 할 부분을 preload한다면 빨리 보여질 것입니다. 
미래의 여러 곳에서 사용될거라 생각되는 자원들은 prefetch 하라

preload 는 브라우저에게 필요한 자원을 일찍이 받아 우선적으로 보여야할 자산에서 사용

prefetch 는 받아온 요청이나 자원이 유저의 앞으로의 페이지를 위한 것에 유용합니다.

 

2. 리소스 용량 줄이기

 

리소스 용량을 줄임으로써 리소스 다운로드 시간을 최적화할 수 있습니다.

 

개발자 도구의 필터에서 리소스의 유형을 보겠습니다.

XHR 

xml http request 로 브라우저단에서 서버단으로 http 비동기 통신할 떄 request 전문이 어떻게 구성되어 서버로 전달되는지와 서버로 부터 요청에 따른 Response 결과를 확인하는 용도이므로 성능 최적화에서는 제외하겠습니다.

 

JS

1) 트리 쉐이킹

외부 모듈에서 필요한 기능만을 임포트 하는 것으로 파일 사이즈를 줄일 수 있습니다. 

// 모든 배열 유틸리티들을 가져온다.
import arrayUtils from 'array-utils'; // bad

// 유틸의 일부만 가져온다.
import {unique, implode, explode} from 'array-utils'; // good

2) 불필요한 코드 제거 

 

3) tab size 2 spaces 권장

 

4) 압축(Minify ) 및 난독화(Uglify) 

👀 Minify (압축) : 소스코드 중 아래와 같은 경우를 제거하는 작업입니다.
- 불필요한 줄바꿈, 공백 및 들여쓰기
- 짧게 쓸 수 있는 긴 구문(if 구문, 형 변환 축약 등)
- 스코프 내 사용하지 않는 변수 , 무의미한 매서드 호출
- 주석
- console.log  
👀  Uglify (난독화) : 코드 자체를 분석하기 어렵게 만드는 과정
- 민감한 코드를 보기 어렵게 만드는 방법 
- 경우에 따라서는 스크립트의 수행 속도에도 영향을 미침
- 변수명, 함수명 치환에서부터 자바스크립트 루틴을 문자열에 바꿔 변수에 담고 뒤섞는 방법도 있다.

 

CSS

1) 간결한 셀렉터를 사용하자

2) 공통 스타일은 class 정의 후 사용하자

3) 이미지일 경우, png 보다 jpg,jpeg 의 크기가 더 작으므로 jpg,jpeg 의 확장자를 사용하는 것이 유리합니다.

4) 애니메이션이 적용된 요소일 경우, gif 보다 video 태그로 mp4 파일을 사용하는 것이 적은 용량의 리소스를 요청합니다.

5) 폰트는 렌더링 트리가 완성된 후 렌더링 트레에서 글꼴 버전이 명시되어 있으면 폰트를 요청하고, 페인트 단계에서 설정합니다.

  - 요청 시점과 페인트 시점 차이로 인해 텍스트가 보여지지 않기도 하여, preload 나 font-display 속성을 @font-face에 추가하여 제어할 수 있습니다. 

 

3. 리소스 요청 개수 줄이기

리소스 요청 개수를 줄이는 것이 사용자에게 더 빠르게 페이지를 로드하는 방법입니다.

1) 이미지 요청 개수를 줄이기

- 이미지 스프라이트

이미지가 다수 사용되는 웹 서비스에서, 각각의 이미지 파일을 서버로 요청하는 것보다, 이미지를 하나로 묶어 한번의 리소스 요청을 통해 가져와 background-position 속성으로 원하는 부분만 표시하는 기법입니다.

 

- 이미지 지연로딩

이미지가 다수 필요한 서비스 ( 쇼핑?) 등에서 사용자 화면에 보이는 이미지만 요청하고, 사용자가 스크롤을 내려 다른 이미지가 보여야 할 떄 이미지를 요청하는 지연 로딩을 통해 리소스 요청을 줄일 수 있습니다. 

👉 사용자가 스크롤을 내려 표시해야 할때 추후에 로드되기( 지연 로드)

 

2) CSS JS 요청 개수를 줄이기

- 모듈 번들러로 css와 js 번들링하기

webpack 과 같은 모듈 번들러로 여러개의 js 파일을 하나의 파일로 번들링할 수 있습니다. 

 

출처: google developers: web performance optimization with webpack

- 캐싱할 필요없는 style은 내부 스타일 시트 사용하기

<link>로 가져오는 외부 스타일 시트가 아닌 , <style> 태그로 포함되는 시트를 사용하여 css 요청 횟수를 줄일 수 있습니다.

대신 style 시트는 캐싱되지 않습니다.

 

- css, js 파일을 캐싱해서 사용하기

웹 캐시란, 애플리케이션을 빠르게 처리하기 위해 client에서 server로 정적 컨텐츠(js,css,이미지) 등을 요청할 때, 이것을 캐시에 저장해두고 , 해당 컨텐츠를 재호출할 때 서버 요청을 통하지 않고 캐시에서 가져와 사용가능합니다.

- 브라우저 캐시, 프록시 캐시, 게이트웨이 캐시 가 있습니다..

 


페이지 렌더링 최적화 

레이아웃 과정은 각 요소들의 화면 상 실제 위치를 계산하기 때문에 비용이 큽니다.

사용자가 DOM 요소를 추가 수정 하면 위치를 다시 계산해야 하기 때문에 레이아웃이 다시 발생합니다.

 

따라서 렌더링 최적화의 목표는 레이아웃을 최대한 적고 빠르게 발생시킵니다.

 

1) 강제 동기식 레이아웃과 레이아웃 쓰레싱 피하기

 

👀 강제 동기식 레이아웃 : 레이아웃 과정이 끝나기 전에 JS 파일에서 DOM 요소의 위차나 크기값을 변경 후 바로 가져오려고 하면 강제로 레이아웃을 시키는데 이것을 강제 동기식 레이아웃이라고 합니다. 
👀  레이아웃 쓰레싱 : 이 동기식 레이아웃을 반복문 내에서 연속적으로 사용하는 것입니다.

 

2) 상위 DOM 보다 하위 DOM 요소를 사용하기

상위 DOM 요소를 사용하면 하위 DOM 요소에도 영향을 미치기 쉬우므로, 가급적 하위 DOM 요소를 변경하는게 유리합니다.

 

3) display : none 은 레이아웃이 되지 않는 이점이 있습니다. 

 

4) <> </> 태그를 이용한다.

Document Fragment 인데 메모리상에만 존재하고 DOM 트리 내부에는 존재하지 않은 노드 조각입니다.

 

5) 시각적인 변화는 setTimeout 보다 requestAnimationFrame이 유리하다.

 

6) CSS 에 복잡한 셀렉터 규칙 사용하지 않기

CSS 가 복잡하고 많을수록 스타일 계산과 레이아웃 과정이 오래걸립니다.

 

7) DOM 트리와 style 트리를 복잡하게 구성하지 않기

 

8) 애니메이션 요소는 position을 고정하기

다른 요소에 영향을 미칠 수 있으므로 position : absolute 나 fixed 로 고정하는 것이 좋습니다.

 

9) 레이아웃보다는 리페인트를 발생시키는 속성을 활용하자

 


성능 측정

성능 개선을 하기 위해서는 현재 성능을 측정해야 합니다. 

 

사용자 기준 성능 지표

사용자에게  컨텐츠를 보여지는 시점을 기반으로 4가지로 나뉩니다.

1. First Paint : 화면에 어떤 요소가 페인트된 시점

2. First Contentful Paint : 화면에 이미지나 텍스트가 나타난 시점

3. First Meaningful Paint : 화면에 의미있는 컨텐츠가 나타난 시점

4. Time to Interactive : JS 초기 실행이 완료되고, 사용자가 인터랙션할 수 있는 시점

 

이 중 Firtst Meaningful Paint가 가장 중요한 지표로 , 성능을 측정합니다. 

 

 

스켈레톤 UX 활용

유튜브를 보게 되면 로드될 위치에 스켈레톤 UX 를 띄우게 되면 체감 속도를 향상시킬 수 있습니다.

 

 

'프론트엔드' 카테고리의 다른 글

Lazy Loading  (0) 2022.09.22
브라우저 동작 원리  (0) 2022.09.08

브라우저 동작 원리

2022. 9. 8. 09:43

 

👉 프론트엔드 성능 최적화를 위해서는 브라우저 동작 원리부터 이해하고 있어야 합니다. 

처음 프로젝트를 진행함에 있어서는 구현이 가장 우선이었습니다. 

그 후에 유지 보수가 필요하거나 페이지 랜더링이 느리게 되는 페이지가 있어 성능 최적화가 필수적이었습니다. 

 

성능최적화를 하기 위해서는어떤 부분에서 최적화를 시킬 수 있고, 성능을 어떻게 향상시킬 것인지 인지할 수 있게 하기 위해  브라우저의 동작 원리를 이해해야 한다고 생각했습니다.

 

브라우저 동작원리

https://www.naver.com을 주소창에 입력하게 되면 내부적으로 어떻게 동작하는지 알아보겠습니다.

 

1. 사용자 브라우저의 호스트파일, 브라우저 캐시에 해당 url 정보가 있는지 확인

 

이전에 접속한 적이 있는지 확인하고, 캐싱이 적용되었다면 별도의 dns 요청없이 url 을 띄울 수 있습니다.

 

👆 dns 요청 ? 

doman name system :  실제 ip 주소로 요청하는 것이 아니라 보기 편한 문자로 요청할 수 있게 해주는 시스템입니다.

1. 브라우저가 www.naver.com  을 요청하면 

2. 네임서버에 www.naver.com  에 해당하는 실제 서버주소를 찾아서 브라우저에서 전달합니다.

3. 브라우저는 전달받은 실제 서버 ip 주소로 요청합니다.

 

 

2. dns에 실 IP 주소를 요청하고, 리소스를 받을 준비

 

호스트의 도메인 이름을 실제 IP 주소를 변환하기 위해 DNS 에 요청합니다. 이제 이 IP 주소의 서버에게 리소스를 요청하여 받아올 준비를 마쳤습니다.   

 

3. HTML, CSS 파싱

 

서버로부터 받아온 파일 중 HTML 과 CSS를 각각 DOM Tree, Style Tree 으로 파싱합니다.

 

4. 렌더링 트리 생성

 

DOM 트리와 Style Tree의 시각 정보를 연결하는 Attachment 작업을 통해 렌더링 트리를 생성합니다.

렌더링 트리는 실제 페이지에서 사용되는 노드만을 포함하며, 루트부터 탐사하며 해당 노드에 일치하는 스타일을 연결합니다.

 

참고 : https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko

 

5. 레이아웃 으로 실제 위치 계산

 

렌더링 트리는 계산된 스타일만을 연결한 것이지, 위치를 계산하지는 않았습니다. 

즉 페이지 내의 실제 위치를 계산하는 작업이 필요합니다.

루트부터 탐사하면서 노드의 화면 상 실제 위치를 절대값(px)으로 계산합니다.

 

6. 페인트 메소드 호출

 

레이아웃 작업이 완료되면, 페인트 메소드를 호출하여 '래스터화'합니다.

렌더링 트리의 각 노드를 화면에 실제 그릴 수 있는 레이어를 생성합니다. 

 

7. 레이어 합성

 

앞선 레이어들을 합성하면 사용자에게 보여줄 화면이 완성됩니다.

페이지 로드가 끝나고 사용자 인터렉션으로 DOM 규칙이나 CSS 규칙이 수정되어 화면이 다시 렌더링되어야 할 경우에는 위의 단계들이 다시 실행됩니다.

 

따라서 위의 렌더링 프로세스를 최적화하는 것이 곧 성능 최적화라고 할 수 있겠습니다. 
1~5 단계를 수행할 때 걸리는 시간을 최소화하는게 렌더링 프로세스를 최적화하는 방법입니다.
초기 렌더링 시간을 줄이고, 화면 업데이트 사이의 시간을 줄여줄 수 있습니다. 

 

 

 

'프론트엔드' 카테고리의 다른 글

Lazy Loading  (0) 2022.09.22
프론트엔드 성능 최적화  (1) 2022.09.08

+ Recent posts