13. 슬라이드 인 🎿

EEuglena·2023년 8월 9일
0

JavaScript30

목록 보기
14/27
post-thumbnail

목표

스크롤이 되어 화면에 들어올 때 슬라이드 인 되는 이미지를 만들어 보자.

스크립트 작성?

오늘 과제는 뭔가 심상치 않다. 핵심만 생각한다면, HTML에 img 요소 몇 개 넣어놓고 숨긴 상태에서 화면 밖으로 translate 했다가 스크롤 이벤트 발생했을 때 화면 안으로 되돌리면 된다.

스크롤 이벤트는 원하는 스타일에 따라 나타나고 사라지는 타이밍을 잘 조절하면 되는데, 오프셋을 화면 높이만큼 주어서 화면 아래쪽에서 나타나는 순간 변화가 일어나도록 했다.

그럼 중요한 것은 무엇이냐? 스크롤 이벤트는 스크롤을 하는 동안 계속 발생하는데, 일반적인 사이트를 처음부터 끝까지 스크롤하면 수백번의 스크롤 이벤트가 발생하게 된다. 즉 핵심 로직을 스크롤 이벤트에 등록하면 성능이 크게 떨어질 수도 있는 것이다. 이를 방지하기 위한 두 가지 기법인 "디바운싱"과 "스로틀링"이 이번 과제의 핵심이라고 할 수 있다. 참고로 강의에서는 디바운싱을 이용했지만 보통 스크롤 이벤트는 스로틀링으로 많이 처리한다.

디바운싱

디바운싱(Debouncing)이란 연속해서 일어나는 이벤트를 하나로 묶어서 처리하는 것이다. 연속한 것 중 첫번째를 처리할지 마지막을 처리할지는 선택할 수 있다. 예를 들어, TV 리모콘의 전원 버튼을 꾹 누르고 있다고 해서 TV가 계속해서 켜지고 꺼지는 것을 반복하지는 않는다. 첫번째 신호로 켜진 뒤에 연속해서 들어오는 신호는 무시하는 것이다. 반대로 검색창에 뭔가 입력하다가 잠시 멈추면 그제야 추천 검색어나 검색 결과가 나타난다면 이는 연속된 입력 이벤트 중 마지막만을 처리하는 것이다. 두 번째 예시처럼 디바운싱은 검색창과 AJAX 요청에서 많이 사용된다. "javascript"를 검색하는 과정에서 "j", "ja", "jav", "java" 등의 이벤트가 차례대로 발생하는데, 이 각각의 입력에 대해 쿼리를 보내면 리소스가 낭비되기 때문이다. 더군다나 "j"나 "ja"에 대한 검색 결과는 유의미하지도 않을 것이다.

보통은 Lodash 라이브러리에서 지원하는 _.debounce를 이용하지만 강의는 바닐라 JS를 지향하기 때문에 직접 구현했다. 디바운싱은 위와 같이 구현할 수 있다. 이벤트가 발생했을 때 실행할 함수를 func로 전달하여 사용하면 된다. immediate이 true일 때는 첫 번째 이벤트, false일 때는 마지막 이벤트를 감지한다.

immediate이 true이면, 첫 번째 실행에서 func가 호출되고 이후로는 계속 setTimeout을 호출한다. 연속해서 이벤트가 발생하더라도 timeout이 존재하기 때문에 func는 호출되지 않고, timeout이 끝난 뒤에 다시 이벤트가 발생하였을 때만 func를 다시 호출한다.

immediate이 false라면, 첫 번째 실행에서 setTimeout이 호출된다. 타이머가 끝나기 전에 이벤트가 연속으로 발생하면 타이머가 계속 초기화된다. 그러다가 추가로 이벤트가 발생하지 않은 채 타이머가 종료되면 그 때 func가 호출된다.

스로틀링

스로틀링(Throttling)이란 이벤트가 발생하고 일정 시간 동안은 동일한 이벤트를 무시하는 것이다. 주로 무한 스크롤을 구현하는 데 사용된다. 디바운싱은 연속한 이벤트를 모두 무시하는데, 무한 스크롤은 주기적으로 스크롤의 위치를 확인해야 하기 때문에 디바운싱으로는 구현할 수 없다. 스로틀링은 정해진 시간 동안만 무시하기 때문에 실행 횟수는 줄이면서 주기적 실행을 보장해 준다. 코드로는 다음과 같이 구현할 수 있다.

첫 이벤트 발생시 원하는 함수를 실행하고, 정해진 시간 동안은 timeout을 설정해 반복 실행이 되지 않게 막는다. 시간이 지나면 timeout이 null로 설정되기 때문에 다시 이벤트를 감지할 수 있는 상태가 된다.

클로저

처음에 강의 코드를 봤을 때 이해가 되지 않았다. debounce라는 함수 내에서 timeout을 선언하고, debounce 안에 새로운 함수에서 그 값을 참조하고, setTimeout을 호출하는 작업의 작동 방식이 이해가 되지 않았다. debounce 함수는 스크롤이 발생될 때마다 새로 호출될 텐데 어떻게 이전 실행에서의 timeout이 그대로 남아있을 수 있지?

const outer = () => {
	let variable;		// let 과 var 의 작동 방식이 다름
    return () => {
    	variable...
    };
};

debounce 함수에서 내가 의문을 느낀 부분을 간략하게 정리하면 위와 같다. 이런 식으로 함수 내부의 변수를 참조하는 또 다른 내부 함수를 클로저(Closure)라고 부른다. 내부 함수에서 변수를 참조할 경우, 외부 함수가 호출되는 환경을 기억하고 있으며 가비지 콜렉터에 의해 변수가 해제되지 않는다. 클로저에 관해서는 비동기 및 반복 실행에서 let과 var의 작동 방식 차이, private 변수 및 함수 활용, 가비지 콜렉션, 메모리 누수 등 다양한 이슈가 있지만 이번 과제에서는 함수 호출이 종료되더라도 메모리가 해제되지 않아 유연한 활용이 가능하다는 점만 알아두면 될 것 같다.

새로 알게 된 점

강의의 의도였는지는 모르겠으나 디바운싱, 스로틀링, 클로저 등 생소한 개념을 많이 알게 되었다. 처음 debounce 함수를 봤을 때 여태 본 함수들과는 생긴 게 너무 달라서 당황스러웠는데 다행히 관련 자료가 많아 큰 도움이 되었다. 디바운싱과 스로틀링은 많이 사용되는 기술이라 라이브러리가 있기는 하지만 직접 구현하면서 원리를 익히는 것도 중요하다. 비동기 실행과 클로저도 다소 직관적이지 않아 이해가 어려웠는데 매개변수를 조금씩 바꿔보기도 하고 함수 호출 순서를 바꿔보기도 하면서 직접 가지고 놀아보니 한층 익숙해진 것 같다.

소스 코드

Visit Github Repository

1개의 댓글

comment-user-thumbnail
2023년 8월 9일

말씀대로 대부분 라이브러리를 활용하지만, 원리를 이해하시려는 것이 너무 멋집니다! 응원합니다 :)

답글 달기