FOUC와 해결방안

FOUC는 외부의 css를 불러오기 전에 스타일이 적용되지 않은 웹페이지가 나타나는 현상이다.
즉 브라우저의 CRP에서 Render Tree가 노출된 후 css와 js로 인해 DOM이 변경되면 변경사항을 적용하기 전 화면이 노출되는 현상이다.
IE에서 주로 발생한다.(IE11에서도 여전히 발생한다고 한다.)

@Import를 사용한 css

IE를 제외한 브라우저는 @import된 스타일이 적용될 때까지 화면에 표시하지 않는다.
하지만 IE는 화면에 노출된 상태로 스타일을 적용하여 FOUC를 유발한다.

다음과 같은 해결방법이 있다.

Preload 사용하기

preload 속성을 사용하여 중요한 css를 렌더링 이전에 사용할 수 있도록 한다.

css 링크 옮기기

head 요소안에 css를 링크하는 방식으로도 해결할 수 있다.

렌더링 지연시키기

render block 요소인 <link rel="stylesheet">을 사용하여 렌더링을 고의로 지연시켜 해결할 수 있다.

Proper caching 적용하기

  • 브라우저 캐시 혹은 cdn을 사용하여 정적 리소스를 받아오는 시간을 줄인다.

웹 폰트의 사용

@import를 사용하여 스타일링을 할 때와 같은 원리로 FOUC가 발생한다. IE는 웹폰트를 사용할 경우 기본 폰트를 불러들여 화면에 노출시키고 이후 사용된 웹 폰트로 다시 변경하기 때문에 발생한다.

다음과 같은 해결방법이 있다.

물론 위에서 언급한 preload와 같은 해결방법도 유효하다.

Font Loading API 사용하기

Font Face Observer 라이브러리는 웹 폰트의 로딩 상태를 추적할 수 있는 폰트 로더로, 파일 크기가 작고 실행속도가 빠르다는 장점이 있다.

다음과 같이 사용할 수 있다.

웹 폰트가 적용되지 않은 상태와 적용한 상태의 css를 적어둔다. 그 후 적용되지 않은 상태의 css가 먼저 적용되도록 한다.

1
2
3
4
5
6
7
body {
font-family: "Apple SD Gothic Neo", sans-serif;
}

body.fonts-loaded {
font-family: "Roboto", "Apple SD Gothic Neo" sans-serif;
}

그 후 사용할 웹 폰트로 FontFaceObserver의 인스턴스를 만들고 load 이벤트를 등록한다.

1
2
3
4
5
let font = newFontFaceObserver("Roboto");

font.load().then(function () {
document.body.classList.add("fonts-loaded");
});

font-display 속성

font-display 속성을 사용하여 웹 폰트의 로딩 상태에 따른 동작을 설정할 수 있다.
swap 속성을 통해 fallback font를 사용할 수 있다.

fallback font로 글자를 렌더링하고 로딩이 완료되면 웹 폰트를 적용한다.

1
font-display: swap;

SSR

SSR의 경우 SSR 단계에서 스타일을 해주어 해결할 수 있다.

공식문서나 바벨 참조하기,,^^

요약

웹폰트 - preload하여 사전에 받아와라, 폴백 폰트를 준비해라
css - 폰트보단 우선순위가 떨어지지만 preload해라. 안 될경우 head에서 받아와라.
최후의 방법으로는 렌더링을 지연시켜라..

Ref

FOUC(Flash of Unstyled Content)
웹 폰트 사용과 최적화의 최근 동향

css in js는 정답일까?

최근의 컴포넌트 형식의 개발방법에 비해 css는 독립적으로 구성할 수 없다.

css를 포함하는 StyleSheet는 js를 포함하는 script와는 상관없이 과정이 진행되기 때문이다.

CSS in JS

CSS in JS 방식의 라이브러리는 js를 사용하여 컴포넌트 단위로 스코프를 나누었다.

js 변수로 선언된 css를 실제 stylesheet로 만들고 랜덤 문자열로 이루어진 클래스명을 만들어 컴포넌트 끼리 학실하게 분리될 수 있게 한다.
(@emotion/css와 같은 css in js 라이브러리를 다루면서 props를 통해 css를 변경할 때 html의 head 태그 안에 새로운 요소들이 추가되는 것을 볼 수 있다.)

게다가 Sass 문법까지 사용 가능하며 변수 사용이 가능하여 클래스 중첩으로 제어하던 부분도 제어할 수 있다.

문제점

장점이 많아보이지만 다음과 같은 단점이 있다.

Script의 코드 증가

StyleSheet의 Script 변환은 HTML 파싱에 사용되는 Script의 코드가 늘어났다는 것을 의미한다.

브라우저 렌더링이 StyleSheet와 Script로 나누어 병렬처리 되던 것이 오직 Script로 이루어짐에 따라 그만큼 속도가 느려진다.

FOUC (Flash of unstyled content)

또한 CSS가 먼저 제공되어 렌더링시 형태가 잡혀있는 기존 방식에 비해 컴포넌트가 렌더링 되며 형태가 잡히기 때문에 원형의 모습이 잠깐 노출(FOUC)된다. 이는 사용자 경험을 저하시킨다.

해결을 위해 빌드시, 사용하는 style을 뽑아서 StyleSheet를 생성해 올려주는 기능이 또 필요하다.

SSR의 문제가 겹친다면 더욱 해결하기 어렵다.
(과거 nextjs에서 css in JS 방식을 사용하였을 때 해당 문제를 겪은 적이 있다.)

정리

css를 사용할지 css in js를 사용할지는 버그의 가능성과 성능 측면에서 고민해야할 것 같다.

Ref

CSS in JS는 무조건 더 좋을까?

requestAnimationFrame 메소드

주기적으로 실행되는 애니메이션을 window.requestAnimationFrame()으로 최적화 해보자

requestAnimationFrame(callback)

브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트 하는 함수를 호출하게 한다. 이 메소드는 리페인트 이전에 실행할 함수를 인자로 받는다.

  • 다음 리페인트에서 그 다음 프레임을 애니메이트 하려면 콜백 루틴이 반드시 스스로 requestAnimationFrame()을 호출해야 한다.

대부분의 브라우저에서 W3C 권장사항에 따라 디스플레이 주사율만큼 호출되게 된다.

callback

파라미터인 callback은 다음 리페인트를 위한 애니메이션을 업데이트 할 때 호출할 함수이다. 콜백함수에는 requestAnimationFrame()이 콜백함수 실행을 시작할 때의 시점을 나타내는 DOMHighResTimeStamp() 단일 인자를 전달한다.

반환 값

requestAnimationFrame()을 취소할 수 있는 고유 id인 long 정수값이 반환된다.
window.cancelAnimationFrame()함수로 전달하여 취소할 수 있다.

차이점

setInterval과는 다음과 같은 차이점이 있다.

주사율만큼의 interval

setInterval을 사용하여 구현할 경우 interval을 손수 설정해주어야 한다.
requestAnimationFrame은 주사율만큼의 Interval을 가지게 된다.(설정해줄 필요가 없다.)

동시 실행

여러개의 setInterval을 사용할 경우 콜백이 겹쳐져서 버벅임이 발생할 수 있다. requestAnimationFrame을 사용할 경우 계속 실행하기 위해선 내부 callback에서 반드시 재호출해야 하므로 여러개의 애니메이션을 써도 버벅이지 않는다.

비동기

js-que

위의 동시실행과 같은 이야기지만 setTimeout, setInterval은 마이크로 태스크큐에서 작동한다. 하지만 requestAnimationFrame은 Animation Frame에서 동작한다. 때문에 setInterval과 달리 callback이 유실될 가능성이 없다.

예시

1
2
3
4
5
6
7
8
9
10
11
12
let start = null;
let hi = 0;
function callback(timestamp) {
if (!start) start = timestamp;
console.log("cnt", hi++);

if (timestamp - start <= 1000) {
window.requestAnimationFrame(callback);
}
}
window.requestAnimationFrame(callback);
// 61번 출력되었다.

Ref

window.requestAnimationFrame()

css 최적화

구글 라이트하우스를 통해 css 최적화에 대한 오류와 기법들이 보고된다.

기본 전제

css 최적화에는 두 가지 기본 전제가 있다.

  1. unused css 제거
  2. render-blocking resources제거

unused css 제거

css는 페이지 렌더링을 차단하는 리소스이기 떄문에 사용되지 않는 css는 브라우저가 스타일을 계산하는데에 잠재적으로 더 많은 시간을 소비하게 만든다.

render-blocking resorces 제거

브라우저가 외부 리소스를 다운로드 하고 파싱하는 동안 페이지 콘텐츠를 파싱하거나 렌더링하지 않기 때문에 페이지 표시 속도 저하의 원인이다.

Unused CSS는 render blocking을 가중하는 요인이다.

render blocking resources

렌더 블로킹 리소스 표시 조건:

  • defer , async 속성이 없는 <head> 요소의 <script>태그
  • mdeia 속성과 값이 없는 <link rel='stylesheet'>태그

script의 여러 속성

  • async
    병렬 다운로드, 즉 스크립트를 다운로드 하면서 웹페이지를 해석한다.
    다운이 끝나면 즉시 실행한다.
  • defer
    병렬 다운로드, 즉 스크립트를 다운로드 하면서 웹페이지를 해석한다.
    그 후 다운로드가 끝나면 웹페이지가 그려지고 DOM이 들어왔을 때 실행한다.

script 개선방안

  1. 필수 스크립트는 head에 <script>형식을 작성한다.
  2. 기타 스크립트는 </body> 종료 태그 직전에 선언한다.
  3. 마지막에 파싱해도 문제가 없다면 defer속성을 사용한다.
  4. 가능한 빠른 시점에 실행이 필요하면 async속성을 사용한다.

css 의 여러 속성

css에 media속성이 없거나 값이 all이면 렌더 차단 리소스이다.

media 속성을 통해 특정 조건에서만 css를 해석하도록 처리하였기 떄문에 render blok resource로 파악되지 않는다.

따라서 반응형 웹 개발시에는 해상도 구간별로 별도의 css를 작성 후 여기에 media 쿼리 구문을 적용하여 개발하면 성능을 개선할 수 있다.

css 개선방안

  • 반응형 웹인 경우 해상도 구간 별로 css 파일을 분리하고 media 속성으로 분기하기
1
2
3
4
5
6
7
<link href="*.css" rel="stylesheet" media=" (max-width:639) " />
<link
href="*.css"
rel="stylesheet"
media=" (max-width:639) and (max-width:960)"
/>
<link href="*.css" rel="stylesheet" media=" (max-width:961) " />
  • 필수 스타일은 페이지의 <head><style> 형식으로 작성하기
1
2
3
<style>
/*필수 스타일*/
</style>
  • 지연 스타일은 <link rel="preload"> 속성으로 병렬 로딩 후 지연 적용하기
    당장 필요하지 않은 css의 경우 병렬로 로딩을 한 후 늦게 화면이 되도록 지연 적용하기

    병렬로 로딩하다가 로딩이 끝나면 onload이벤트를 이용하여 rel을 stylesheet로 적용하여 화면에 적용된다.
    중요하지 않은 CSS 연기

    1
    2
    3
    4
    5
    6
    <link
    rel="preload"
    as="style"
    href="x.css"
    onload="this.onload=null;this.rel='stylesheet'"
    />

LCP(Largest Contentful Paint)

가장 큰 덩어리 콘텐츠를 2.5초 이내에 로딩시켜야 한다.

LCP 개선 사례

  1. 라이브러리 의존도 줄이기
    사용하려는 기능 외에 많은 기능을 포함하고 있기 떄문에 FCP를 늦출 확률이 높다.
    ex)jquery lodash,normalize

    https://youmightnotneed.com을 통해 라이브러리 대신 사용할 수 있는 바닐라 js를 찾을 수 있다.

  2. 사용하지 않는 css 제거
    normalize나 reset과 같이 잘 사용하지 않는 css를 과감하게 버려야한다.

  3. preconnect/preload
    웹 폰트를 preconnect하여 url에 미리 연결하고 바로 다운받을 수 있게 도와준다.
    또한 css를 preload하여 렌더링을 차단하지 않고 css를 로드한다.

    1
    2
    3
    4
    5
    6
    7
    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link
    rel="preload"
    as="style"
    href="x.css"
    onload="this.onload=null;this.rel='stylesheet'"
    />

LCP를 head에서 preload로 로딩하여 성능을 개선할 수 있다.

  1. feature detection

type과 media라는 조건을 붙여서 최적화한다.

1
2
3
4
5
6
<picture>
<source srcset="small.avif" type="image/avif" media=" (max-width:640px)" />
<source srcset="small.avif" type="image/avif" />
<source srcset="small.webp" type="image/webp" media=" (max-width:640px)" />
<source srcset="small.webp" type="image/webp" />
</picture>
  1. Image Loading / Decoding

loading="lazy"는 뷰포트 안에 들어오지 않으면 로딩을 하지 않는다.

또한 decoding="async"는 화면에 다른 요소를 렌더링하는 걸 중단하지 않고 다른 요소를 먼저 표시하고 이미지를 나중에 표시하는 기법이다.

1
<img src="example.jpg" loading="lazy" decoding="async" />

CLS(Cumulative Layout Shift)

누적 배치 변경이라고 하며 이미 배치가 끝난 컨텐츠의 위치가 바뀌는 상황이다.

0부터 1까지의 범위를 가지고 있으며 0.1 이내로 단축하는 것이 좋다.

CLS 유발 요인

  1. 치수를 알 수 없는 이미지 로딩.
  2. 동적으로 추가된 DOM.
  3. 웹 폰트 swap 페인팅(FOIT/FOUT)

CLS 해결방법

  1. 자리표시자
    동적으로 추가하는 컨텐츠에 들어올 영역에 자리 표시자를 미리 띄워놓고 이미지가 들어오지 않더라도 그 영역을 다른 요소가 침범하지 않도록 막아놓는다.

    • 최소크기를 지정하기

    이미지나 영상이 로딩될 영역의 최소크기를 지정한다.

    1
    2
    3
    4
    .heroBanner {
    min-height: 100px;
    background: silver;
    }
  2. 이미지/ 영상 요소에 비율 힌트 제공

    • img태그에 width와 height값의 비율을 제공하기
      css로 이미지의 크기를 설정할 수 있다고 하더라도 img 태그에 제공 해주는 것이 좋다.
    1
    2
    <img src="..." width="800" height="534" alt />
    <!-- + max-width:100%; height:auto; -->
    • aspect-ratio
      aspect-ratio는 비율을 고정하는 속성인데 아직 많이 지원되지는 않는다.

    • 영상 종횡비 유지하기(padding)
      padding 속성에 %를 주어 부모요소를 기준으로 하게 하여 비율을 유지하게 해준다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .utube {
    position: relative;
    padding-top: 56.25%;
    }
    .utube__iframe {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    }
  3. 웹폰트 대체 글꼴 지정하기

텍스트 영역의 높이가 늘어나면서 CLS가 발생한다.
비슷한 특징을 갖는 대체 글꼴을 사용하여 바뀌지 않도록 해야한다.

1
2
3
* {
font-family: "Noto Sans KR", Verdana, sans-serif;
}
  1. 애니메이션 적용 시 transform 사용.

reset,normalize

대부분의 초기화 스타일은 쓸모 없거나 덮어쓰는 코드가 대부분이다.

이는 unusedcss를 증가시킨다.

reset.css 최적화하기

1
2
3
4
5
6
7
8
9
10
11
12
body {
margin: 0;
overflow-wrap: break-word;
}
:lang(ko) {
word-break: keep-all;
}

img {
max-width: 100%;
height: auto;
}

클래스 속성이 들어간 요소들에만 reset.css를 적용한다.

링크

1
2
3
[class]{
...
}

선택자 최적화

선택자를 많이 중첩시키지 않는 것이 중요하다.

FE 성능 최적화

패스트캠퍼스 the red를 듣고 정리해보았다.

파일 다운로드

최신 브라우저는 대체로 도메인 당 6개의 접속만 동시에 처리한다(HTTP 프로토콜 /1.1기준)
http2에서는 하나의 연결로 계속 파일을 주고받아서 괜찮다.
하지만 아직은 1.1기준으로 작동하는 사이트가 많다.

한 도메인에서 6개의 파일만 동시에 다운로드 할 수 있다.
6개 이상부터는 큐에 넣어두고 하나씩 다운로드 하게 된다.

이미지와 같은 파일들은 사용자가 기다리는것에 익숙하지만
js나 cs파일들의 다운로드가 느려지면 페이지에 변화가 없고 인터액션이 늦어지게 된다.

개선방안

  1. 파일 갯수와 용량을 작게한다.

  2. 별도 도메인이나 CDN등으로 분산하여 해결할 수 있다.

  3. 적절한 포맷을 사용한다. 이미지의 경우 webp파일을 사용하여 크기를 줄일 수 있다.
    ex) 레진코믹스

1
2
3
4
5
6
<!-- 지원 타입에 따라 다른 이미지 로딩 -->
<!-- img 태그를 picture태그로 감싸서 picture태그를 지원하는 브라우저에서는 picture를 해석하도록 만들었다. -->
<picture>
<source srcset="img/photo.webp" type="image/webp" />
<img src="img/photo.jpg" alt="my photo" />
</picture>
  1. 화면 크기와 해상도에 따라 적절한 이미지 로딩
1
2
3
4
5
6
7
8
<!-- 화면 크기에 따라 적절한 이미지 로딩 -->
<picture>
<source srcset="img/photo_small.jpg" media="(max-width:800px)" />
<img src="img/photo.jpg" alt="my photo" />
</picture>

<!-- 해상도에 따라 적절한 이미지 로딩 -->
<img src="img/icon72.png" alt="icon" srcset="img/icon14.png 2x" />
  1. 웹 폰트 최적화
  • 구글폰트: 나눔 고딕 등 한국어 폰트 26종 제공
  • WOFF2는 WOFF,TTF등에 비해 30% 용량이 작다.
  • 필요한 글자만 골라서 글꼴을 만들 수도 있다.(메뉴용 글꼴 등)
  1. 화면 크기 등에 따라 필요한 스타일 시트만 로딩
  • 하지만 실제 이렇게 사용하는 경우는 없다. css는 크기가 작고 파일 개수를 늘리는 것은 좋지 않다.
  • 부트스트랩이 차지하는 용량이 20kb쯤
1
2
3
4
5
6
<link href="mobile.css" rel="stylesheet" media="all" />
<link
href="desktop.css"
rel="stylesheet"
media="screen and (min-width: 600px)"
/>
  1. link 태그 만으로 접속 시간 절약
1
2
3
<link rel="dns-prefetch" media="https://taegon.kim" />
<link rel="preconnect" media="https://cdn.example.com" />
<link rel="prefetch" media="https://cdn.example.com/library.min.js" />

Preconnect를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결해둔다. 서드파티 자원 연결에 적합하다.

Prefetch를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결 후 다운로드까지 해둔다.
하지만 남용하면 안되고 꼭 필요하거나 다음 경로가 명확하게 예상이 될 경우에만 사용해야 한다.

로딩 속도 개선

  1. 필수 컨텐츠가 아니라면 비동기 로딩을 고려해보자(광고,댓글,헤더/푸터 등)

  2. 이미지/아이프레임/스크립트 등은 필요할 때까지는 읽지 않는 게으른 로딩(lazy loading)기법을 고려해보자. 코드를 분할한 후 필요할 때 읽어오는 방식이다. 웹팩에서 자동으로 해주기도 한다.

1
2
<img src="image.jpg" loading="lazy" />
<iframe src="https://example.com" loading="lazy"></iframe>
  1. 시간이 많이 걸린다면 플레이스 홀더 등으로 대체한다.

-스켈레톤 ui

계산 시간

  1. 웹 워커

자바스크립트는 싱글스레드이다. 하지만 워커스레드를 이용하여 멀티스레드가 가능하다.

워커스레드

  • UI를 조작할 수 없다.
  • 워커 스레드 전용으로 분리된 파일이 필요하다.
  • postMessage()로 데이터를 전송하고, onMessage 이벤트를 통해 받는 방식으로 통신한다.
  1. 느긋한 계산

값이 필요해질 때 까지 계산을 미뤄두는 기법이다.

게으른 평가, 지연 평가라고도 한다.

  1. 메모이제이션

계산 결과를 기억해두고 반복 사용하는 기법.
루프, 재귀 호출 등 최적화.

반응시간

지나치게 느린 애니메이션 및 응답 속도는 사용자 경험을 저해한다.

  1. 시스템의응답이 40ms보다 느리면 주의력 분산이 발생함
  2. ui가 100ms 이하로 반응해야 ui를 다루고 있다고 느낀다.
  3. 애니메이션은 60fps를 기준으로 한다.

애니메이션의 렌더링 순서

js-> 스타일-> 레이아웃 ->페인트 -> 합성

페인트까지는 메인스레드에서 진행한다. 합성만 컴포지터 스레드에서 진행한다.

메인스레드는 자바스크립트가 동작하기 때문에 메인스레드의 작업이 많아지면 자바스크립트의 속도도 느려진다.

개선 방법

메인스레드에서 이루어지는 과정은 가능한 한 피하고 컴포지터 스레드에서 일어나도록 하면 된다.

즉 레이아웃이나 페인트는 피하고 합성단계를 유발하는 css 속성들을 주로 사용한다.

각 단계의 동작을 유발하는 css 속성은 https://csstriggers.com/에서 확인할 수 있다.

정리

  1. 가능하면 js보다는 css 애니메이션을 최대한 활용한다.
  2. 다음 css 속성 위주로 애니메이션 -gpu 가속을 적용한다.(자동으로 적용된다.)
  • transform:translate or scale or rotate
  • opacity
  1. 레이아웃 변경이나 리페인팅을 유발하는 css 속성은 비용이 많이 든다.
  • 레이아웃 : width,height,padding, margin,display
  • 페인트 : color,background,outline,box-shadow
  1. setTimeOut 보다는 requestAnimationFrame, Web Animations API 등 활용
  • requestAnimationFrame: 브라우저가 최적화, 비활성 탭에서는 동작 안 함
  • web Animation API: css애니메이션과 같은 애니메이션 엔진 사용
  1. three.js velocity.js와 같은 고성능 애니메이션 라이브러리 사용

  2. will-change 속성을 통해 브라우저가 최적화 할 속성 명시

  • 남용하면 리소스 낭비,
  1. DOM 접근과 업데이트는 가능한 적게, 한 번에 몰아서 처리,
  • 엘리먼트 추가는 DocumentFragment 활용
1
2
3
4
5
6
7
8
9
10
const app = document.getElementById("app");
const frag = document.createDocumentFragment();

for (let i = 0; i < 1001; i++) {
const el = document.createElement("div");
el.innerText = `Element ${i}`;
frag.appendChild(el);
}

app.appendChild(frag);

리소스

cpu 점유율, 전력 소비량, 스토리지, 메모리 사용량, 네트워크 트래픽 중 메모리 누수와 네트워크 트래픽을 신경써야 한다.

메모리 누수

프로그램이 필요하지 않는 메모리를 계속 점유하는 현상

메모리 생명 주기

  1. 할당 : 사용할 메모리를 확보한다.
  2. 사용 : 메모리를 사용한다.
  3. 헤재 : 불필요한 메모리를 반환한다.

개선 방법

js는 가비지 컬렉션을 통해 메모리를 해제한다.

이 메모리를 몇개의 변수가 참조하는지 카운트하여 0개일 경우 지운다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var x = {
a: {
b: 2,
},
};
// 두 객체 모두 생성된다.
// x 객체를 o1 a객체를 o2라고 하자

var y = x; //y도 o1를 참조한다.

x = 1; // x는 이제 o1를 참조하지 않는다. 이제 o1를 참조하는 변수는 y가 유일하다.

var z = y.a; // o1 내부의 o2을 z가 참조한다.

y = "mozilla"; // y는 o1를 참조하지 않지만 z변수가 o1의 o2를 참조하므로 o1은 제거되지 않는다.

z = null; // 이제 o1과 o2는 아무 곳에서도 참조가 되지않아 가비지 컬렉션이 된다.

순환참조 문제

객체끼리 참조가 맞물려서 가비지 컬렉션이 동작하지 않는 문제

마크스위프 알고리즘을 통해 해결할 수 있게 되었다.

메모리 누수의 대표적 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
}

const fn=factory();
fn();
// fn을 실행한 후에도 여전히 largeData는 메모리에 존재한다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

개선방안

아래의 코드는 예시일 뿐이다.

실제로는 모듈이나 다른 함수로 분리해서 사용하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
};

{
const fn=factory();
fn();
}
// fn은 실행 후 현재의 Lexical Enviroment와 함께 해제되고 largeData도 함께 해제된다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

네트워크 트래픽

파일 용량을 줄이거나 필요할 때만 불러와서 트래픽 낭비를 줄인다.

  1. 최소화된 js,css파일을 사용한다.
  2. 프레임워크는 한 개 이하만 사용한다.
  3. 파일 주소의 파라미터는 주의해서 사용, 의도하지 않은 캐시버스터가 될 수 있다.
  • 브라우저는 전체 url 기반으로 캐시하기 때문에 업데이트가 되어도 받아오지 못할 수 있다.
  • 일부러 랜덤한 숫자를 붙여 새로 캐시되도록 하는 것이 캐시버스터이다.
  1. 이미지, 미디어는 필요할 때 불러오는 게으른 로딩(lazy loading)기법을 사용한다.

js 색의 밝기 정보 추출하기

그래프의 배경색에 따라 글씨 색을 다르게 하려는 작업을 위해 구글링 하던 중

색의 밝기 정보를 알아내는 코드를 찾게 되었다.

1
2
3
4
5
6
7
8
let c = c.substring(1); //  # 제거
let rgb = parseInt(c, 16); // 10진수로 변경
let r = (rgb >> 16) & 0xff; // r추출
let g = (rgb >> 8) & 0xff; // g추출
let b = (rgb >> 0) & 0xff; // b추출

let luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
// luma가 255에 가까울 수록 밝다.

Ref

stackoverflow

Numble 다른 색깔 찾기 게임 제작 챌린지

Numble에서 진행하는 [React] 다른 색깔 찾기 게임 제작 챌린지에 참여하였다.

핵심 로직

  • 여러 상태를 관리하기 위해 useReducer를 사용하였다.

  • useInterval Hook을 정의하여 setInterval의 timer가 React의 state에 맞춰서 작동하도록 하였다.

  • 난이도가 올라갈 수록 어려운 색이 나오도록 하였다.

    • 15스테이지까지는 정답색의 r,g,b값 중 b값과 투명도를 조절하였다.
    • 15~30 스테이지까지는 정답색의 투명도를 0.8로 고정하였다.
    • 30이상의 스테이지에서는 정답색의 투명도를 0.9로 하였다.

활용한 라이브러리와 그 이유

  • React.js를 사용하였다.

    • Next.js로 작성하였으나 Next.js는 Univasal Rendering으로 첫 페이지를 SSR로 렌더링하여 첫 렌더링시 정답 블럭과 정답 색의 불일치가 발생하였다.
      때문에 완성본을 React.js로 다시 작성하였다.
  • @emotion/styled 라이브러리를 사용하였다.

    • grid를 잘 사용해보지 않아서 calc를 통해 블록당 width를 계산하도록 하였다.

프로젝트를 진행할 때 어려웠던 점

  • 데모 버전을 살펴보았는데, 모든것이 시간 기준이라 시간만 state로 관리하고 나머지는 useRef나 useMemo를 이용하여 관리할 수 있다고 생각이 되었다.

    • 이를 예전에 다 구현을 해두었으나 위에서 언급한 Next.js의 렌더링 방식으로 인해 첫 렌더링시 정답 블럭과 정답 색의 불일치가 발생하였다.
    • 이 이슈 해결이 제일 어려웠는데 SSR에 대한 로직이나 플러그인을 설치하는 것 보다 React.js로 옮기는 것이 확실하다고 생각되어 옮겼다.
  • 위에서 언급한 이슈해결에 대해서 고민하다가 넘블 챌린지 공고를 다시 한 번 보았다.

    • 공고를 살펴보던 도중 useReducer라는 Hook이 있다는 것을 알게 되었고, 도입하는 것이 코드를 훨씬 깔끔하게 작성할 수 있다고 생각이 들어서 도입하였다. useReducer를 사용하여 컴포넌트 본문의 길이를 줄이고 가독성 좋은 코드로 수정할 수 있었다.

마무리

리액트의 state에 대해 더 잘 알 수 있어서 유익했다.
또한 상태관리 라이브러리를 사용하지 않고 useReducer라는 Hook을 알게되고 활용 해보아서 좋았다..!

React Image preload!

프론트에서 최적화를 하기 위해 이미지 자체를 webp등으로 최적화하는 방법이 있지만 다른 방법으로는 미리 로딩하고 캐시로 가져오는 preload 방식이 있다.

Preload

이미지를 미리 로딩하고 캐시로 가져오는 방식이다.
캐시는 브라우저차원에서 자동으로 하기 떄문에 이미지를 미리 받아오기만 하면 된다.

Preload 에는 두 가지 방식이 있다.

병렬(Parallel) 방식

자바스크립트의 경우 요청을 병렬로 처리하려고 하므로 for문을 돌면서 여러번 호출해주면 된다.

리액트의 경우 useEffect에서 첫 렌더링시에 호출하여 이미지를 미리 로드해두고 캐시해서 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function preloading(imageArray) {
imageArray.forEach((url) => {
const image = new Image();
image.src = url;
});
}

preloading(["1.png", "2.png", "3.png"]);
}, []);

특징

모든 요청을 한 번에 보내기 때문에 전체 사진을 불러오는데에 시간이 줄어든다. 하지만 전체 사진이 로드되기 전까지 기다려야한다.

modules

순차적(Sequential) 방식

순차적 방식은 재귀를 통해 구현한다. 이미지의 onload 이벤트에 다음 이미지를 parameter로 재귀호출하도록 하여 이미지가 우선순위를 갖고 캐싱될 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useEffect(() => {
function preload(imageArray, index) {
index = index || 0;
if (imageArray && imageArray.length > index) {
const image = new Image();
image.onload = function () {
preload(imageArray, index + 1);
};
image.src = images[index];
}
}

preload(images);
}, []);

특징

병렬 방식에 비해 전체 작업시간은 늘어나지만 이미지들이 하나씩 로드되어 하나씩 확인할 수 있다.

modules

마무리

  • 병렬 : 빠르게 불러와야 할 때

  • 순차적 : 조금씩이라도 불러와야할 때, 추가로 슬라이더 등을 구현할 때 다음 이미지를 이 방식으로 미리 불러오면 좋을 것 같다.

ref

image preloading
이미지 프리로딩

HTML label 태그와 input태그

<label> 태그는 사용자 인터페이스 항목의 설명을 나타내는 태그라고만 간략하게 알고있었다.

하지만 <label> 태그에는 내가 몰랐던 기능이 있었다.

<label>태그를 클릭하면 <input>태그가 클릭된다.

아래의 코드는 <label>태그 안에 <span><input>을 작성한 코드이다.

위에서 설명하였듯이 <label>을 클릭할 경우 <input>태그에도 이벤트가 전달된다.

때문에 여기에서 <span>을 클릭할 경우 이벤트 버블링에 의해서 <label>에도 이벤트가 전파되고 이로 인해 <input> 태그에도 이벤트가 전달된다.

즉 이벤트버블링을 명시적으로 막지 않는 한, <label>안의 요소를 클릭하면 <input>에 이벤트가 전달된다.

1
2
3
4
<label>
<input type="checkbox" />
<span></span>
</label>

useLayoutEffect

리액트의 Hooks 중에는 useLayoutEffect라는 Hook이 있다.
이 에 대해 정리해보았다.

Flow Diagram

Hook FLow Diagram에 따르면 Cleanup LayoutEffects는 브라우저가 페인트 작업을 하기 전에 실행된다.

hookflow

useLayoutEffect

위의 이미지를 보면 useEffect는 페인트 작업이 끝난 후 호출된다.

useLayoutEffect는 다음과 같이 작성하여 사용할 수 있다.

1
2
3
4
5
6
useLayoutEffect(() => {
// func
return () => {
// cleanup
};
}, [dependencies]);

사용 예시

useEffect 내부에서 상태값을 설정한다면
페인트를 마친 후에 상태값을 설정하며 리렌더링 되고 렌더링되지 않았던 값들이 렌더링되며 깜빡거리게 된다.

이럴 경우 useLayoutEffect를 호출하게 되면
페인트를 시작하기 전에 상태값을 설정하게 되고, 처음 렌더링시에 상태값이 이미 설정되어있어 깜빡거리지 않는다.

요약

useEffect 내부에서 상태값을 설정해야 한다면 useLayoutEffect를 사용하여 설정하자.( 물론 처음 화면을 렌더링하는데에 필요한 시간은 길어진다. 트레이드오프!)

ref

hook-flow