해당 주제 선정 이유
프로젝트에서 반응형 적용 가능한 캘린더를 제작하는 과정에서
resizeObserver
를 이용할 일이 있었다. 최적화 방법을 찾다가 쓰로틀링과 디바운싱이라는 개념을 알게 되어 정리해보고자 해당 주제를 선정하게 되었다.
JS에서 이벤트를 다루다보면 무수히 많은 이벤트가 발생하는 경우를 확인할 수 있다. 이런 무의미한 이벤트 호출을 방지하고, 최적화시키기 위해 쓰로틀링과 디바운싱이라는 개념을 이용한다.
이벤트 예시로는 scroll, resize, input, mousemove 등이 있을 수 있다.
쓰로틀링과 디바운싱에 대해 설명하고,
일정 시간 내 이벤트를 한 번만 실행하도록 제어하는 것으로 보통 마지막 함수 호출 이후 일정 시간 후 함수를 호출할 수 있도록 제어한다.
쓰로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다!
window.addEventListener('input', throttle(function(e) {
console.log(e.target.value);
}, 200));
function throttle(func, delay) {
let timer;
return function() {
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
}
}
간단한 input 예제로, 0.2초에 한 번 함수가 실행되도록 만든 경우이다.
주로 scroll 혹은 무한 스크롤 UI 구현 등에서 사용한다.
한 행위를 여러 번 반복할 시, 즉 동일한 이벤트를 여러번 요청할 시에 마지막 행위 이후 특정 시간이 지나야 콜백을 호출할 수 있도록 하는 방식이다,
window.addEventListener('input', debounce(function(e) {
console.log(e.target.value);
}, 200));
function debounce(func, delay) {
let timer;
return function() {
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
}
}
위의 쓰로틀링과 동일한 예제이나 debounce를 적용한 것이다.
주로 resize 이벤트 처리 혹은 ajax 요청을 위한 입력 필드 자동완성 UI 구현, 버튼 중복 클릭 방지 등에 사용한다.
쓰로틀에 대해 익숙하지 않았던 이유 중 하나는 스크롤 이벤트 관리 시, 이미 실무에서는 Intersection Observer API를 사용하고 있었기 때문이다.
Intersection Observer API는 상위 요소 또는 최상위 문서의 viewport와 대상 요소 사이의 변화를 비동기적으로 관찰할 수 있는 수단을 제공합니다.
Intersection Observer API는 특정 요소가 다른 요소와의 교차점에 들어가거나 나갈 때 또는 두 요소 간의 교차점이 지정된 양만큼 변화될 때 실행되는 콜백 함수를 코드에 등록할 수 있습니다.
Intersection Observer는 설명에서도, 또한 사용 사례에서도 알 수 있듯 기존 scroll 이벤트의 문제점을 개선하고자 개발되었다.
왜 Intersection(혹은 교차 관찰자)라는 표현에 대한 의문이 생겼는데, 이는 해당 WebAPI가 루트요소와 타겟요소의 교차 지점을 관찰하기 때문이다.
let options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);
여기서 전달되는 option의 경우 다음과 같은 요소들을 포함할 수 있다.
option 종류
- root 대상 가시성을 체크하기 위한 뷰포트로 사용되는 요소(default: 브라우저 뷰포트)
- rootMargin 루트 주위의 여백 설정
- thereshold 관찰자의 콜백이 무조건 실행되어야 하는 대상의 가시성 백분율을 나타내는 숫자 또는 숫자 배열. 예를 들어서 50% 넘어간 지점에서 콜백을 실행시키고 싶다면 0.5라고 지정하면 된다.
target 요소를 전달한다.
let target = document.querySelector("#listItem");
observer.observe(target);
// observer를 위해 설정한 콜백은 바로 지금 최초로 실행됩니다
// 대상을 관찰자에 할당할 때까지 기다립니다. (타겟이 현재 보이지 않더라도)
이 의문과 관련해서 성능을 직접 비교한 좋은 글이 있었다.
Scroll listener vs Intersection Observers: a performance comparison
Percentage of total time doing scripting work
Scroll Listener — No Caching & No Throttling: 48.9%
Scroll Listener — Caching & No Throttling: 43.5%
Scroll Listener — Caching & Throttling: 28.9%
Intersection Observer: 23.3%
caching 과정을 겹쳐도 intersection observer의 성능이 더 우수함을 알 수 있고, 이 성능은 메인 스레드의 성능을 의미한다. 결국 Intersection Observer를 이용한다는 것은 WebAPI에 이벤트를 위임하는 것이고, 따라서 자연스럽게 메인 스레드의 자유도도 보장한다고 볼 수 있을 것이다.
React에서는 custom hook을 이용해서 공통화 하는 것이 적절했다!
아래는 usehooks의 useIntersectionObserver 예시와 package이나, 간단하게 이용하기에 정의가 그리 어렵진 않으므로 직접 작성하여 사용하여도 무방할 것 같다.
usehooks의 useIntersectionObserver
react-intersection-observer(npm)
requestAnimationFrame을 이용해 쓰로틀링 적용 시, repaint를 개선한 사례에 대한 글이다.
scroll event 최적화로 웹페이지 성능 개선하기
모던 자바스크립트 deep dive(2023)
[Javascript] 디바운싱(debouncing)과 쓰로틀링(throttling)
실무에서 느낀 점을 곁들인 Intersection Observer API 정리