force layout
js에서 요청/호출되는 일부 속성은 브라우저가 스타일과 레이아웃을 동기적으로 계산하도록 한다.
layout trashing
웹 브라우저는 레이아웃 변경을 즉시 처리하지 않고 비슷한 Style의 수정을 모아서 하게 된다. 즉 화면에서 레이아웃에 대한 정보를 실제로 알아야 할 때까지 레이아웃 계산을 지연시킨다.
layout trashing이 발생하는 경우
DOM이 변경되지 않았음을 보장할 수 있는 경우엔 레이아웃 캐시(이전 계산된 값)에서 값을 가져온다.
하지만 offsetHeight과 같은 최신으로 동기화된 레이아웃에 대한 정보에 접근하는 속성같은 경우 강제로 레이아웃 계산(리플로우)를 발생시켜 동기화 후 값을 가져오게 된다.
즉 아래와 같이 코드를 작성하게 되면 불필요한 레이아웃 계산을 과정이 요구된다.
1 | elementA.className = "a-style"; |
layout trashing이 발생하지 않는 경우
위 코드는 다음과 같이 수정할 수 있다.
1 | elementA.className = "a-style"; |
똑같은 작업을 하는 코드지만 위의 코드에 비해 레이아웃 계산의 수가 줄어들게 된다.
레이아웃 계산을 강제로 발생시키는 속성은 여기에서 확인할 수 있다.
연속된 layout trashing 최적화하기
paragraph의 너비를 box의 너비와 같도록 하는 코드를 작성한다고 생각해보자
1 | function resizeAllParagraphsToMatchBlockWidth() { |
위와같이 작성한다면 box의 offsetWidth에 접근한 뒤 paragraphs[i]의 너비로 설정하게 된다.
offsetWidth 속성에 접근할 때마다 강제로 레이아웃 계산을 시도하게 되므로 단일 paragraph마다 layout 계산이 일어나게 된다.
이는 다음과 같이 수정할 수 있다.
1 | function resizeAllParagraphsToMatchBlockWidth() { |
reflow를 강제로 발생시켜 애니메이션 실행하기
브라우저는 비슷한 style변화를 모아서 반영하기 때문에 원하는대로 애니메이션이 작동하지 않을 때가 있다. 이럴 때는 강제로 reflow를 발생시켜 해결할 수 있다.
1 | <h2>no force reflow</h2> |
아래는 css이다. 애니메이션을 해제하는 클래스를 정의하였다.
1 | .wrapper { |
제대로 작동되지 않는 경우
브라우저의 작동원리로 인해 애니메이션이 제대로 작동하지 않는 경우이다.
1 | const $wrapper = document.querySelector(".wrapper"); |
코드만 보았을 때 item의 애니메이션 동작은 다음과 같은 순서로 일어나야 한다.
- 애니메이션 해제(
notransition
추가) - 박스가 부모박스 오른쪽 끝에 붙도록
left
수정 - 애니메이션 설정(
notransition
제거) left
를50px
로 수정
실제로는 다음과 같이 동작한다.
- Reflow(속성에 접근했기 때문)
- style 수정을 통합함
- 애니메이션 설정(
notransition
제거) left
을50px
로 수정(통합된 변경사항)- Repaint
제대로 작동하는 경우
reflow를 강제로 발생시켜 제대로 작동하는 코드이다.
1 | //... |
의도한 애니메이션은 item과 같다.
그리고 의도한대로 작동한다. item2의 동작 순서는 다음과 같다.
- Reflow
- 애니메이션 해제(
notransition
추가) - 빨간 박스가 부모박스 오른쪽 끝에 붙도록
left
수정 - Repaint
- Reflow
- 애니메이션 설정(
notransition
제거) left
를50px
로 수정- Repaint
4와 5의 Repaint와 Reflow는 clientLeft속성을 조회하여 브라우저를 강제로 동기화했기 때문에 일어난다.
실제 작동하는 예시는 여기에서 예제를 확인할 수 있다.