JavaScript

Lazy loading (Intersection observer vs scroll event)

song 2022. 12. 27. 11:18

Data search를 위한 filter를 최적하기 위해 시도했던 내용을 정리한다.

 

filter는 Mui의 Select component를 base로 만들어져 있으며 천개 정도의 menu item을 갖는다. menu popup을 여는데 약 2초 정도가 걸렸고 performance를 돌렸을 때 menu item들이 추가되며 render/commit 둘 다 많은 시간이 소요되는 것을 확인할 수 있었다.

 

1. 미리 DOM tree에 달아놓고 container의 css만 변경하여 popup을 연다.

search filter가 render될 때 모든 item을 미리 함게 render 해놓고 items의 container를 display none으로 둔다. click event가 발생하면 item을 추가하는 것이 아닌 container의 css만 변경하여 사용자에게 보여준다.

  • Menu popup의 open/close가 빠르게 가능하지만 초기 rendering에 시간이 많이 걸린다.
  • Mui의 Select는 click event가 발생했을 때 menu item을 추가하도록 구현되어 있어 동작을 바꾸기 까다롭다.

 

2. Intersection observer 사용

Intersection observer를 사용하여 lazy loading을 하는 방법이다. scroll event와 달리 비동기적으로 실행되기 때문에 main thread에 영향을 주지 않는 장점이 있다(event 다중 등록 및 scroll에 의한 수 많은 callback 실행, getBoundinfClientRect()에 의한 강제 reflow 방지에도 좋다). 하지만 몇 가지 문제가 있어 최종적으로는 적용하지 못 했다.

  1. fillter를 적용할 수 없다.
    • filler: lazy loading될 element의 크기를 미리 계산하여 해당 높이를 차지하도록 넣는 것. scroll size가 실제 모든 element가 render 됐을 때와 동일하게 보이기 때문에 사용자 경험이 좋다.
    • observer는 비동기적으로 그리고 주기적으로 DOM element의 위치는 check하여 callback을 수행한다. 이 때 이 주기는 browser의 render cycle (60fps, 16ms)에 맞춰져 있는데 scroll을 굉장히 빨리하면 주기 내에 element의 visibility 변경이 여러번 이루어져 callback이 실행되지 않는 문제가 있다. 따라서 scroll을 빠르게 내릴 때 data가 loading되지 않는 현상이 발생했다.
  2. lazy loading이 될 때마다 모든 item을 render한다. (filler 없이)
    • Data가 loading될 때마다 scroll이 점점 작아지지만 별로 불합리하진 않은 것 같음.
    • Lazy loading이 될 때마다 item list가 변경되고 container가 re-rendering된다. 기존에 있던 item들은 memo하여 re-rendering이 발생하지 않고 추가되는 item만 rendering이 되어야 하는데 memo할 수 없는 문제가 있었다. MenuItem에 제공되는 click handler가 항상 새로운 reference로 전달되고 있으며 Mui Select 내부적으로 props를 내리기 때문에 useCallback으로 memo할 수도 없었다. 따라서 data가 많이 loading되어 있을 수록 새로운 data를 loading하여 rendering하는 데에 시간이 더 소요되는 문제를 해결할 수 없었다.
  3. 기존 item의 re-render를 하지 않기 위해 삭제
    • 2-2에서 적용한 방법에 문제가 되었던 기존 item의 re-rendering을 막기 위해 새로운 data가 loading되면 그만큼 앞의 item을 삭제하는 방법을 적용해보았다. 성능상의 문제는 발생하지 않지만 scroll을 내릴수록 scroll의 크기는 일정한데 위아래로 왕복운동하는 것처럼 보이는 것이 사용자 경험에 매우 안좋다는 느낌을 받았다.

 

3. Scroll event 사용

Intersection observer에 비해 단점이 있지만 단점을 극복하고 장점을 살려 적용하였다.

 

  1. Event는 container에만 등록하고 throttle을 적용해 callback 호출 횟수를 줄인다.
  2. filler size를 계산하여 scroll의 크기 및 위치를 모든 item render없이 정확히 표시할 수 있다.
  3. 보이는 영역 및 위아래 offset을 적용해 부드러운 scolling이 가능하게 할 수 있다.

 

Summary

Google image, facebook 등 scroll을 내림에 따라 (거의)무한히 data를 fetch해서 추가하는 경우에는 intersection observer를 사용하는 게 좋을 것 같고, menu나 table 같이 contents 양이 많지만 수가 명확히 정해진 경우에는 filler를 넣어서 scroll event로 처리하는 게 좋지 않을까 하는 생각.