무한스크롤에 대해 알아보기

무한스크롤을 구현하는 여러 방법에 대해 알아보자.
풀스택 클론코딩을 모두 마무리한 후에 하나씩 시도해보고 정리해볼 계획이다.

scroll을 이용한 방법

리액트에서 작성한 코드를 가져왔다.

window.scrollY는 스크롤바에서 현재 스크롤의 위치를 나타낸다
document.documentElement.clientHeight는 현재 화면의 높이를 픽셀로 나타낸다.
document.documentElement.scrollHeight은 총 스크롤의 길이를 나타낸다

즉 스크롤을 최하단으로 내렸을 때 아래의 식이 성립한다.

1
2
3
window.scrollY + document.documentElement.clientHeight ===
document.documentElement.scrollHeight;
//true

게시글로 따로 작성하기 애매한 디테일은 next에서 제공하는 throttle 함수는 이미 보내진 요청을 취소할 수는 없어 loading이 true일 때는 아예 요청을 안 보내도록 처리해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
useEffect(() => {
function onScroll() {
if (
window.scrollY + document.documentElement.clientHeight >
document.documentElement.scrollHeight - 300
) {
if (hasMorePost && !loadPostLoading) {
dispatch({
type: LOAD_POST_REQUEST,
});
}
}
}
window.addEventListener("scroll", onScroll);

return () => {
window.removeEventListener("scroll", onScroll);
};
}, [hasMorePost, loadPostLoading]);

Intersection Observer API

작성중..!

react virtualized

작성중..!

바닐라js + string으로 html 다루기

같이 교육을 듣는 팀원분이 코드를 깔끔하게 작성하셔서 참조하였다.
예전 vanilla js 로 컴포넌트로 관리하는 아티클을 읽었을 때도 사용된 방법이다.

string으로 template를 정의한 뒤 바꾸는 방식이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let ul = document.createElement("ul");
let template = `
<li class="d-flex-col justify-content-between">
<div class="py-2 d-flex justify-content-between">
<h2>{{date}}</h2>
<span>{{total}}</span>
</div>
<ul class="px-2 d-flex-col " data-date={{data-date}}></ul>
</li>
`;

template = template.replace("{{data-date}}", ele.date);
template = template.replace("{{date}}", ele.date);
template = template.replace("{{total}}", addComma(ele.total));

ul.innerHTML = template;
transactionHistorybyDocument.append(ul);

ref

상민님

https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Component/#_1-%E1%84%80%E1%85%B5%E1%84%82%E1%85%B3%E1%86%BC-%E1%84%80%E1%85%AE%E1%84%92%E1%85%A7%E1%86%AB

드래그 이벤트 구현하기

드래그

드래그는 클릭 -> 마우스 이동 -> 마우스 떼기로 이루어진다.

마우스 클릭(mousedown)에 이벤트를 등록하고 마우스 떼기(mouseup)에서 이벤트를 지워주면 된다.

또한 브라우저에서 기본으로 제공하는 드래그 이벤트를 없애주면 된다.

mousedown에 mousemove와 mouseup 이벤트를 등록하고 mouse up에서 이를 모두 지워주었다.

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
31
32
33
34
35
36
37
38
39
const slider = document.querySelector(".slider");
const thumb = slider.querySelector(".thumb");
const before = slider.querySelector(".balance__goal .complete");
console.log(before);

thumb.onmousedown = function (event) {
event.preventDefault();

let shiftX = event.clientX - thumb.getBoundingClientRect().left;
let End = slider.offsetWidth - thumb.offsetWidth;

document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);

function onMouseMove(event) {
let newComplete =
event.clientX - shiftX - slider.getBoundingClientRect().left;

if (newComplete < 0) {
newComplete = 0;
}

if (newComplete > End) {
newComplete = End;
}

before.style.width = newComplete + "px";
thumb.style.left = newComplete + "px";
}

function onMouseUp() {
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousemove", onMouseMove);
}
};

thumb.ondragstart = function () {
return false;
};

ref

https://ko.javascript.info/mouse-drag-and-drop

script 태그의 위치

렌더링 엔진은 script태그를 다운로드하거나 실행하는 중에는 파싱을 멈춘다.
자바스크립트 파일은 css나 html을 변경시킬 가능성이 있기 때문이다.

  1. head,body태그 최상단

파싱을 시작하자마자 script 태그를 다운로드한다.
파싱을 막기 때문에 script태그를 먼저 다운로드하고 실행 후 파싱한다.
실행 순서를 정할 수 있다.
다만 존재하지 않는 DOM요소에 접근시 에러가 발생할 수 있다.

  1. body태그 최하단

파싱이 끝난 뒤에 script 태그를 다운로드 시작한다.
js파일에 의존성이 높을경우 사용자경험이 좋지 못하다.

  1. defer

병렬로 백그라운드에서 다운로드한다.
다운로드 하는 중에도 HTML 파싱을 멈추지 않는다.
defer 스크립트의 실행은 페이지 구성이 끝날 때까지 지연된다.

defer 스크립트는 DOM이 준비된 후에 실행되지만 DOMContentLoaded 전에 실행된다.

아래는 예시 코드이다.

1
2
3
4
5
6
7
8
9
<p>...스크립트 앞 콘텐츠...</p>

<script>
document.addEventListener('DOMContentLoaded', () => alert("`defer` 스크립트가 실행된 후, DOM이 준비되었습니다!")); // (2)
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<p>...스크립트 뒤 콘텐츠...</p>
  1. 페이지의 콘텐츠가 모두 출력된다.

  2. DOMContentLoaded 이벤트는 defer 스크립트가 실행된 후에 발생하므로 alert창은 DOM트리가 완성되고 defer 스크립트의 실행이 완료된 후에 실행된다.

  3. async

async도 마찬가지로 백그라운드에서 다운로드 된다. 따라서 HTML 페이지는 async 스크립트 다운이 완료되길 기다리지 않고 페이지 내 컨텐츠를 출력한다.( 하지만 async 스크립트 실행중에는 HTML 파싱이 멈춘다.)

DOMContentLoaded 이벤트와 async 스크립트는 서로를 기다리지 않는다.

  • DOM 트리를 다 만든 후에 async 스크립트 다운로드가 끝났을 경우 DOMContentLoaded 이벤트는 async 스크립트 실행 전에 발생할 수 있다.
  • 마찬가지로 async 스크립트가 DOM트리를 만들기 전에 다운로드 되었을 경우 async 스크립트의 실행이 끝난 다음 DOMContentLoaded 이벤트가 발생한다.

아래는 예제 코드이다.

1
2
3
4
5
6
7
8
9
10
<p>...스크립트 앞 콘텐츠...</p>

<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM이 준비 되었습니다!"));
</script>

<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>

<p>...스크립트 뒤 콘텐츠...</p>

async 스크립트는 그 특징 떄문에 실행순서를 보장할 수 없다. 먼저 다운로드가 끝난 스크립트 순으로 실행된다. 즉 small.js가 long.js보다 먼저 실행된다.

또한 alert의 실행시점도 예측할 수 없다.

이러한 특징으로 인해 구글 어널리틱스같은 독립적인 역할을 하는 서드파티 스크립트를 삽입할 때에 유용하다. 개발중인 스크립트에 의존하지 않고 독립적으로 동작하기 때문이다.

1
<script async src="https://google-analytics.com/analytics.js"></script>

Ref

defer, async 스크립트

브라우저의 랜더링 과정에 대해 알아보자 2 최적화

size of HTML

  1. HTML의 크기를 최소로 해야한다.
  • 사전처리 및 상황별 최적화

    • 주석들을 없애야함(브라우저로 주석을 보내 줄 이유가 없다.)
    • 헤더의 인코딩방식을 다르게 한다.
    • 반복되는 데이터를 압축(AAA=>3A)
    • 중복되는 style 정의를 합친다.
    • 공백(스페이스나 탭)을 제거한다.
  1. 압축해야한다.
  • GZIP을 이용한 텍스트 압축
    • GZIP은 텍스트 기반 압축 툴이다.
    • 모든 최신 브라우저는 이를 지원하고 자도으로 요청한다.
    • 일부 CDN의 경우 GZIP이 활성화 되었는지 확인해야한다.
    • 개발자 도구 네트워크탭에서 Size/Content열을 통해 압축된 크기를 확인할 수 있다.

사전처리 및 상황별 최적화 후 GZIP을 적용하여 최소화된 출력을 압축하면 큰 절감효과를 얻을 수 있다.

  1. 브라우저에 의해 캐싱해야한다.
  • 서버를 통해 리소스를 받아오기 전까지 랜더링이 되지 않기 떄문에 캐싱을 통해 서버로의 요청을 줄이면 최적화할 수 있다.
  • 브라우저의 모든 HTTP요청은 브라우저 캐시로 라우팅되어 유효한 캐시가 있는지 먼저 확인한다.
  • 이는 요청 헤더와 응답헤더의 조합에 의해 제어된다.
  • 기본적으로 브라우저는 사용자를 대신하여 헤더설정을 관리한다.( 자동으로 캐싱한다. )
  • 웹서버에서 Cache-Control헤더를 통해 관리할 수 있다.

Unblocking CSS

  • HTML,CSS는 기본적으로 랜더링 차단 리소스이다.
  • 랜더링하려면 렌더트리가 필요하고, 렌더트리를 만들기 위해선 서버에서 CSS를 가져올 때 까지 기다려야한다.(HTML 파싱은 한다.)
  • 미디어 유형과 미디어 쿼리를 통해 일부 css를 비차단 리소스로 표시할 수 있다.
  • 혹은 inline css를 사용하여 요청을 하지 않을 수 있다.
  • 브라우저는 차단동작이든 비차단 동작이든 관계없이 모든 CSS 리소스를 다운로드 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
body {
font-size: 16px;
}

@media screen and (orientation: landscape) {
.menu {
float: right;
}
}

@media print {
body {
font-size: 12px;
}
}
  • print 파일을 분리된 파일로 옯긴다.
  • 미디어 속성을 통해 해당 css가 언제 적용될 지 브라우저에게 전달할 수 있다.(비차단 리소스로 표시하기)
1
2
3
4
5
6
7
8
9
body {
font-size: 16px;
}

@media screen and (orientation: landscape) {
.menu {
float: right;
}
}
1
2
3
4
5
@media print {
body {
font-size: 12px;
}
}
1
2
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="style-print.css" media="print">

JavaScript and CRP

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/adding-interactivity-with-javascript#parser-blocking-vs-asynchronous-javascript

  • JS 는 DOM과 CSSOM 모두 조작 가능

  • script 태그를 만났을 때는 DOM 생성을 중단하고 자바스크립트가 실행되기를 기다려야한다

  • script 태그가 DOM 생성을 막기 때문에 js는 parser blocking이다.

  • 인라인 스크립트 대신 외부파일로 스크립트를 사용할 경우엔 파서가 script 태그를 발견하면 파일을 받아서 실행한다. 파일을 가져오는 동안에 DOM 생성을 계속할 수 없으므로 CRP가 늦어진다.

  • 인라인 스크립트를 사용하는 것은 요청을 줄이는 데에 도움을 주지만 코드가 반복되고 과도하게 사용될 수 있는 단점이 있다.

  • js는 CSS를 조작할 가능성도 있기 때문에 script는 css가 도착하고 CSSOM을 생성할 때 까지 실행되지 않는다.(CSSOM 생성 후 JS가 실행된다.) 따라서 js 최적화는 css최적화와 깊은 연관이 있다.

  • 사용자 분석 등 랜더링에 영향을 주지 않는 스크립트

    • 랜더링이 끝나고 브라우저가 onload 이벤트를 발생 시켰을 때 실행하여 최적화 할 수 있다.
    • script 태그에 async 속성(CRP를 막지 않는 script)을 통해 최적화 할 수 있다. async 속성을 붙일경우 브라우저의 DOM 생성을 막지 않는다. script 요청을 처리하고 dom을 파싱한다. 또한 CSSOM에 영향을 받지 않는다. CSSOM생성 전에 script를 사용 가능하다면 바로 실행할 수 있다.
    1
    <script src="analytics.js" async></script>
    • 자바스크립트를 css위에 넣어서 css에 방해받지 않고 실행할 수 있다.

요약

  • 데이터통신의 양을 줄임
  • 중요한 리소스의 개수를 줄임
  • CRP의 길이를 줄인다
    • 서버와 클라이언트를 왕복하는 수

Preload Scanner

  • 문서의 앞부분에서 CSS나 js를 알아내려 한다.
  • 파서가 막힌동안 js나 css를 다운로드받는다.(병렬 요청)

브라우저의 랜더링 과정에 대해 알아보자 1 CRP

브라우저의 랜더링 과정과 CRP에 대해 알아보자

Critical Rendering Path

  • 브라우저가 HTML,CSS,Javascript를 화면에 실제 픽셀로 변환하는 단계의 순서.
  1. HTML을 가져와서 DOM(Document Object Model)을 생성한다.
  2. CSS를 가져와서 CSSOM(CSS Object Model)을 생성한다.
  3. 둘을 합쳐서 Render Tree를 만든다.
  4. 레이아웃 단계를 거쳐서 모든 것이 페이지의 어느 위치에 갈 것인지 정한다.
  5. 실제 화면에 픽셀을 그린다.

Converting HTML to the DOM

  • URL을 요청하고 엔터를 누르면 브라우저가 서버로 HTML 요청을 보낸다.
  1. 브라우저가 HTML을 읽어와서 지정된 인코딩에 따라 개별문자로 변환한다.
  2. HTML은 태그를 만날 때 마다 W3C HTML5표준에 맞춰 토큰을 만든다.
  3. 토큰은 해당 속성 및 규칙을 정의하는 객체로 변환된다.
  4. HTML 마크업 간의 관계를 정의하기 떄문에 생성된 객체는 트리 데이터 구조 내에 연결된다.(노드가 된다.) 이는 상하위 관계도 포함한다. 즉 HTML객체는 body의 상위이고, body는 paragraph 객체의 상위이다.

이러한 구조로 부분 HTML을 먼저 로드하여 성능개선을 할 수 있다.

Converting CSS to the CSSOM

  1. 유효한 토큰이 존재하는지 확인한다.
  2. 토큰을 노드화한다.

부분 css의 사용은 불가능하다. 잘못된 스타일을 사용 할 수 있기 때문이다.
브라우저는 모든 CSS를 받고 처리할 때 까지 페이지 랜더링을 차단한다.

  • 선택자의 차이
1
2
3
4
5
6
h1 {
font-size: 16px;
}
div p {
font-weight: 12px;
}

첫 번째 태그는 h1에 일괄적으로 적용하지만 두 번째 태그는 모든 p 태그를 찾고
부모노드가 div인 것을 찾아서 스타일을 적용하기 때문에 브라우저가 더 많은 작업을 해야한다.

The Render Tree

  • DOM 과 CSSOM트리를 합친 트리이다.
  • 눈에 보이는 내용만 포함한 트리이다.
    • display:none과 같은 style이 있는 노드의 경우 포함되지 않는다.
    • html태그는 눈에 보이는 내용이 없으므로 제거한다.

Layout

  • width
    • %의 경우 메타태그에서 설정한 뷰포트의 너비 or 부모의 너비를 상속한다.
    • 아래의 태그의 경우 뷰포트 너비를 디바이스의 너비로 설정한다. 기본값은 980px이다.
      1
      <meta name="viewport" content="width=device-width">
  • 스타일이나 내용을 변경하여 렌더트리를 업데이트 할 경우 레이아웃 단계를 다시 실행할 가능성이 크다.
  • 뷰포트 너비가 변하면 브라우저는 레이아웃 단계를 다시 시행해야 한다.
    (폰을 돌리거나 브라우저 크기를 조정할 떄 일어난다.)

이를 피하기 위해 여러번의 레이아웃 이벤트를 피하고자 업데이트를 한 번에 반영하는 것이다.

크롬 개발자 도구 perfomance탭 알아보기

크롬의 개발자도구의 성능분석 탭을 사용해보자.
다음 문서의 예시를 따라 진행하였다.

동작원리

성능 분석은 웹페이지를 녹화하고 분석하는 방식으로 진행된다.

시작하기

성능(Perfomance)탭은 크롬 개발자도구에 있다.

다음과 같은 순서로 시작할 수 있다.

  1. 개발자 도구를 열고 perfomance탭에 들어간다.

성능 탭

  1. 예제를 신행하고 싶을 경우 다음 링크를 시크릿 모드에서 연다. 크롬 익스텐션이 성능 측정에 노이즈를 발생시킬 수 있기 때문이다.

  2. 시작한다.

    • 초기 랜더링부터 분석 하고싶은 경우 새로고침 버튼을 누르면 된다.
    • 혹은 성능을 분석하고 싶은 시점 전에 녹화버튼을 누르면 된다.
    • stop버튼을 눌러 녹화를 중지할 수 있다.
    • 너무 길게 녹화할 필요 없이 3~4초 정도 녹화하면 된다.

모바일 CPU 시뮬레이션

모바일 기기는 데스크탑이나 노트북에 비해 CPU의 성능이 좋지않다.
CPU의 성능을 일부러 저하시켜 모바일 환경에서의 시뮬레이션을 할 수 있다.

  1. 성능 탭에서 스크린샷(screenshot)이 활성되어있는지 확인한다.
  2. 캡쳐 설정(톱니바퀴)를 클릭한다.
  3. 아래의 CPU제한없음에서 CPU에 제한을 준다.
    예시에선 2배 제한을 줬지만 예전 아티클인지 4배 8배만 남아있어서 4배 제한을 주었다.

모바일 시뮬레이션

결과 분석

위의 시작하기에서 얻은 결과를 분석해보자.

분석 결과

초당 프레임 분석

애니메이션의 성능을 측정하는 주요 지표는 초당 프레임수(FPS)이다.
사용자들은 애니메이션이 60 FPS로 실행될 때 행복하다.

  1. FPS 차트
    빨간색 막대를 볼 때마다 Framerate가 저하된다는 의미이다.
    녹색 막대가 높을수록 FPS가 높다.

    FPS 차트

  2. CPU 차트
    CPU 차트의 색상은 그림에서 아래의 요약탭의 색상과 일치한다.
    색칠이 되어있을 수록 CPU를 사용했다는 의미이다.
    장시간 색칠되어있을 경우 최적화가 필요하다는 의미이다.
    CPU 차트

FPS,CPU 차트 위에 마우스를 올리면 해당 시점의 스크린샷을 보여준다.

  1. 프레임
    프레임
    마우스를 올리면 특정 프레임에 대한 FPS를 보여준다.
    위 사진과 Frame 영역의 색이 다른 건 사각형 수를 조절했기 때문이다.

    • 실시간 FPS 추정치
      ctrl+shift+p를 누르고 FPS 추정치를 확인할 수 있다.
      실시간 FPS 추정치

    다음과 같이 실시간으로 FPS 추정치를 보여준다.

병목 현상 찾기

  1. 요약 탭

    • 선택한 이벤트가 없는 경우 활동 내역을 보여준다.
    1. 사각형 수가 없는 경우
      유휴상태가 많고 작업이 여유로움을 알 수 있다.

    사각형 수가 없는 경우

    1. 사각형 수가 많은 경우
      사각형 수가 늘어날 수록 랜더링에 대부분의 시간이 쓰이는 것을 알 수 있다.

    사각형 수가 많은 경우

    1. 사각형 수가 많은 경우+ 최적화
      여전히 랜더링에 대부분의 시간이 쓰이지만
      애니메이션을 담당하는 스크립트에서 작업시간을 줄였음을 알 수 있다.

    최적화를 해줄 경우

  2. 기본 탭
    특정 프레임을 클릭하고 기본탭을 열어 시간 경과에 따른 메인스레드의 활동도를 볼 수 있다.
    각 막대는 이벤트이며 막대의 길이가 길 수록 오래 걸렸다는 것을 의미한다.
    또한 막대가 쌓인 것은 아래의 이벤트가 위의 이벤트를 발생 시켰다는 의미이다.
    기본 탭

    또한 이벤트를 클릭하여 단일 이벤트에 대한 정보를 확인할 수도 있다.
    기본 탭 단일 이벤트

    호출 스택을 통해 이벤트를 발생시킨 소스코드 또한 확인할 수 있다.

    이벤트를 발생시킨 소스코드

프론트엔드 개발에 도움되는 사이트 모음

HTML

CSS

CSS Validation

Palate

Gradient

Font

Icon

optimization

  • PageSpeed

    페이지의 성능을 검사하고 개선해야할 사항을 알려준다. 한글을 지원한다.
    https://pagespeed.web.dev/