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; }
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를 사용할지는 버그의 가능성과 성능 측면에서 고민해야할 것 같다.
주기적으로 실행되는 애니메이션을 window.requestAnimationFrame()으로 최적화 해보자
requestAnimationFrame(callback)
브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트 하는 함수를 호출하게 한다. 이 메소드는 리페인트 이전에 실행할 함수를 인자로 받는다.
다음 리페인트에서 그 다음 프레임을 애니메이트 하려면 콜백 루틴이 반드시 스스로 requestAnimationFrame()을 호출해야 한다.
대부분의 브라우저에서 W3C 권장사항에 따라 디스플레이 주사율만큼 호출되게 된다.
callback
파라미터인 callback은 다음 리페인트를 위한 애니메이션을 업데이트 할 때 호출할 함수이다. 콜백함수에는 requestAnimationFrame()이 콜백함수 실행을 시작할 때의 시점을 나타내는 DOMHighResTimeStamp() 단일 인자를 전달한다.
반환 값
requestAnimationFrame()을 취소할 수 있는 고유 id인 long 정수값이 반환된다. window.cancelAnimationFrame()함수로 전달하여 취소할 수 있다.
차이점
setInterval과는 다음과 같은 차이점이 있다.
주사율만큼의 interval
setInterval을 사용하여 구현할 경우 interval을 손수 설정해주어야 한다. requestAnimationFrame은 주사율만큼의 Interval을 가지게 된다.(설정해줄 필요가 없다.)
동시 실행
여러개의 setInterval을 사용할 경우 콜백이 겹쳐져서 버벅임이 발생할 수 있다. requestAnimationFrame을 사용할 경우 계속 실행하기 위해선 내부 callback에서 반드시 재호출해야 하므로 여러개의 애니메이션을 써도 버벅이지 않는다.
비동기
위의 동시실행과 같은 이야기지만 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; functioncallback(timestamp) { if (!start) start = timestamp; console.log("cnt", hi++);
최신 브라우저는 대체로 도메인 당 6개의 접속만 동시에 처리한다(HTTP 프로토콜 /1.1기준) http2에서는 하나의 연결로 계속 파일을 주고받아서 괜찮다. 하지만 아직은 1.1기준으로 작동하는 사이트가 많다.
한 도메인에서 6개의 파일만 동시에 다운로드 할 수 있다. 6개 이상부터는 큐에 넣어두고 하나씩 다운로드 하게 된다.
이미지와 같은 파일들은 사용자가 기다리는것에 익숙하지만 js나 cs파일들의 다운로드가 느려지면 페이지에 변화가 없고 인터액션이 늦어지게 된다.
개선방안
파일 갯수와 용량을 작게한다.
별도 도메인이나 CDN등으로 분산하여 해결할 수 있다.
적절한 포맷을 사용한다. 이미지의 경우 webp파일을 사용하여 크기를 줄일 수 있다. ex) 레진코믹스
1 2 3 4 5 6
<!-- 지원 타입에 따라 다른 이미지 로딩 --> <!-- img 태그를 picture태그로 감싸서 picture태그를 지원하는 브라우저에서는 picture를 해석하도록 만들었다. --> <picture> <sourcesrcset="img/photo.webp"type="image/webp" /> <imgsrc="img/photo.jpg"alt="my photo" /> </picture>
화면 크기와 해상도에 따라 적절한 이미지 로딩
1 2 3 4 5 6 7 8
<!-- 화면 크기에 따라 적절한 이미지 로딩 --> <picture> <sourcesrcset="img/photo_small.jpg"media="(max-width:800px)" /> <imgsrc="img/photo.jpg"alt="my photo" /> </picture>
<!-- 해상도에 따라 적절한 이미지 로딩 --> <imgsrc="img/icon72.png"alt="icon"srcset="img/icon14.png 2x" />
웹 폰트 최적화
구글폰트: 나눔 고딕 등 한국어 폰트 26종 제공
WOFF2는 WOFF,TTF등에 비해 30% 용량이 작다.
필요한 글자만 골라서 글꼴을 만들 수도 있다.(메뉴용 글꼴 등)
화면 크기 등에 따라 필요한 스타일 시트만 로딩
하지만 실제 이렇게 사용하는 경우는 없다. css는 크기가 작고 파일 개수를 늘리는 것은 좋지 않다.
부트스트랩이 차지하는 용량이 20kb쯤
1 2 3 4 5 6
<linkhref="mobile.css"rel="stylesheet"media="all" /> <link href="desktop.css" rel="stylesheet" media="screen and (min-width: 600px)" />
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에 가까울 수록 밝다.