본문 바로가기

Frontend

offsetWidth으로 강제로 랜더링시키기

728x90

로그인과 회원가입 폼을 만들 때 로그인 화면에서 회원가입 버튼을 누르면 아래처럼 로그인 모달이 180도 가로로 뒤집히면서 회원가입 폼이 나타나게 하고 싶었다.

 

그래서 처음에는 180도 뒤집는 flip 애니메이션을 만든 뒤에 회원가입 버튼과 로그인 버튼의 click 이벤트 핸들러를 다음과 같이 작성했다.

function animateForm() {
  const formWrapper = document.querySelector(".auth_form_wrapper");
  formWrapper.classList.remove("animate_flip_reverse"); // 반대 애니메이션 제거
  formWrapper.classList.add("animate_flip"); // 애니메이션 추가
}

function animateFormReverse() {
  const formWrapper = document.querySelector(".auth_form_wrapper");
  formWrapper.classList.remove("animate_flip");
  formWrapper.classList.add("animate_flip_reverse");
}
.animate_flip {
  animation: ease-in-out 1s forwards flip;
}

.animate_flip_reverse {
  animation: ease-in-out 1s forwards reverse flip;
}

@keyframes flip {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(180deg);
  }
}

 

그랬더니 처음 한 번만 애니메이션이 재생되고 그 이후에는 뚝뚝 끊기기 시작했다.

 

 

구글링을 해보니 void formWrapper.offsetWidth; 을 코드에 추가하라고 했다.

function animateForm() {
  const formWrapper = document.querySelector(".auth_form_wrapper");
  formWrapper.classList.remove("animate_flip_reverse");
  void formWrapper.offsetWidth;
  formWrapper.classList.add("animate_flip");
}

 

그랬더니 잘 돌아갔다. 왜지…? 이유를 이해하기 위해서 브라우저의 랜더링 과정과 reflow와 repaint를 간단히 이해하기로 했다.

브라우저 랜더링 과정

  1. html을 parsing해서 DOM(document object model) Tree를 만든다.
  2. css를 parsing해서 CSSOM(css object model) Tree를 만든다. (거의 html과 동시에 이루어짐)
  3. DOM과 CSSOM이 결합된 Render Tree를 만든다.
  4. viewport 내에서 요소들의 정확한 위치와 크기를 정하는 layout 단계를 거친다.
  5. 요소들을 화면의 실제 픽셀로 변환하는 paint 단계를 거친다. 이때 픽셀로 변환된 결과는 여러 레이어로 관리된다.
  6. paint 단계에서 생성된 여러 레이어를 합성해서 실제 화면에 그리는 composite 단계를 거친다.

브라우저의 리랜더링

브라우저는 한 번만 랜더링 되는 것이 아니라 애니메이션, 사용자의 인터랙션 등으로 인해 리랜더링되기도 한다. 이때 처음부터 모든 랜더링 과정을 거치는 게 아니라 변화되는 속성에 따라 효율적으로 랜더링 과정을 거치기 위해 나오는 개념이 reflow와 repaint다.

Reflow

reflow는 position, width, height 등 주로 위치나 box-model과 관련된 속성들이 변화할 때 일어난다. 즉 요소의 위치와 크기 변화가 일어나면 layout 단계부터 다시 해야하기 때문에 발생한다.

Repaint

페인팅은 색상 등이 변해 요소들의 배치 위치와 크기는 변하지 않고 다시 그려야할 때 발생한다. layout 단계를 거치지 않아도 되기 때문에 reflow보다는 상대적으로 부하가 적은 작업이다.

그렇다면 void xxx.offsetWidth의 의미는?

offsetWidth는 요소의 가로 값을 가져오는데 최신 상태의 가로 값을 가져오기 위해서는 reflow를 발생시키기고 리랜더링을 하게 된다. 즉, 강제로 리랜더링되면서 애니메이션이 실행되도록 하는 것이다.

왜 강제로 리랜더링을 해야하는 것일까?

브라우저는 dom 변경 사항을 배치 처리하기 때문에 애니메이션 클래스의 더하기 빼기가 제대로 작동하지 않을 수 있다. 브라우저는 성능 최적화 때문에 변경 사항이 너무 빠르게 발생하면 이를 같이 처리하기 때문이다.

참고

https://velog.io/@zizonyoungjun/브라우저-렌더링과-JS에서의-스타일-속성-조작

https://medium.com/개발자의품격/브라우저의-렌더링-과정-5c01c4158ce

https://velog.io/@young_pallete/Reflow-Repaint을-알아보자

https://enjoydev.life/blog/frontend/2-browser-rendering

https://stackoverflow.com/questions/60686489/what-purpose-does-void-element-offsetwidth-serve

반응형