개발/Javascript

[자바스크립트] setInterval 대신 requestAnimationFrame 사용하기

duknock 2023. 4. 27. 10:38
반응형

 

로그인 세션의 남은 시간을 나타내는 타이머

 

 

 

보통 브라우저 화면에 타이머 같은것을 띄울 때, setInterval 함수를 사용한다.

 

 

 

<html>

<head>
	<meta charset="UTF-8">
	<title>타이머</title>
</head>

<body>
	<h1>타이머</h1>
	<p id="timer">0초</p>
	<button onclick="startTimer()">시작</button>
	<button onclick="stopTimer()">정지</button>
	<script>
		// 변수 선언
		let seconds = 0;
		let timerInterval;

		// 타이머 시작 함수
		function startTimer() {
			// 1초마다 seconds 변수 증가
			timerInterval = setInterval(function() {
				seconds++;
				// 타이머 렌더링
				document.getElementById("timer").innerHTML = seconds + "초";
			}, 1000);
		}

		// 타이머 정지 함수
		function stopTimer() {
			// setInterval 함수 정지
			clearInterval(timerInterval);
			// seconds 변수 초기화
			seconds = 0;
			// 타이머 렌더링
			document.getElementById("timer").innerHTML = seconds + "초";
		}
	</script>
</body>

</html>

 

 

이런식으로 1초 (1000ms) 마다 함수를 실행하여 HTML 요소를 업데이트하는 방식으로 렌더링하게 되는데

 

 

고작 타이머 하나 보여주려고 백그라운드에서 계속 돌아가는 setinterval 함수를 사용하는 것은 아래와 같은 문제점들이 있다.

 

 

1. 정확한 실행 시간 보장 불가능:
setInterval 함수는 일정한 시간 간격으로 코드를 실행하는 것이 아니라, 이벤트 루프에서 실행 대기열에 추가된 시점부터 일정 시간이 지난 후 실행됩니다. 따라서, 코드 실행이 지연될 가능성이 있습니다.

2. 코드 충돌 가능성:
setInterval 함수를 사용하면서, 두 개 이상의 코드가 동시에 실행될 가능성이 있습니다. 이 경우, 코드 충돌이 발생하여 예상치 못한 결과가 발생할 수 있습니다.

3. 반복 시간 간격의 불일치:
setInterval 함수를 사용하여 일정 시간 간격으로 코드를 실행하면 시간 간격이 정확하게 유지되지 않을 수 있습니다. 이는 브라우저의 성능에 따라 달라질 수 있습니다.

4. 메모리 사용:
setInterval 함수를 사용하면 반복적으로 실행되는 코드가 메모리를 계속해서 사용하게 됩니다. 이는 메모리 누수를 유발할 수 있습니다.

5. 중복 실행:
setInterval 함수를 사용하면 이전 코드 실행이 완료되지 않은 상태에서 새로운 코드가 실행될 가능성이 있습니다. 이러한 경우, 코드 중복 실행이 발생할 수 있습니다.

 

 

 

그래서 단순 HTML 렌더링을 위해서라면 setInterval 함수 대신 사용할 수 있는 좋은 함수가 있다.

 

 

바로 requestAnimationFrame 이라는 함수다.

 

 

 

 

 

 

 

 

 

requestAnimationFrame()

 

 

 

 

 

requestAnimationFrame 함수는 브라우저의 성능을 최적화하고 애니메이션을 부드럽게 처리하기 위한 자바스크립트 함수이다.

 

 

이 함수는 브라우저의 화면 갱신 주기에 맞추어 애니메이션을 처리하므로, 애니메이션의 부드러운 움직임을 보장한다.

 

 

이 함수가 setInterval 함수보다 나은 점은 아래와 같다.

 

 

1. 더 나은 성능: 
requestAnimationFrame 함수는 브라우저의 화면 갱신 주기에 맞추어 호출되기 때문에, 브라우저가 새로운 프레임을 그리기 전까지 실행되지 않는다. 이를 통해, 애니메이션이 부드럽게 처리되고 브라우저의 성능을 최적화할 수 있다. setInterval 함수는 정해진 시간 간격으로 함수를 호출하는 방식으로 애니메이션을 처리하기 때문에 브라우저의 성능과 화면 갱신 주기와 일치하지 않을 수 있다.

2. 메모리 절약:
화면 갱신 주기에 맞추어 호출되기 때문에 불필요한 처리를 방지하고 메모리를 절약할 수 있다. 이는 모바일 기기에서 특히 중요하다. (많은 메모리의 사용은 배터리 수명과 직결되기 때문)

3. 다른 탭과의 경쟁 회피:
requestAnimationFrame 함수는 브라우저가 비활성화되거나 다른 탭으로 전환되어 있는 경우에는 호출되지 않는다. 이는 다른 탭과의 경쟁을 회피하고 브라우저의 성능을 유지하는 데 도움이 된다.

4. 동일한 타이머 공유:
여러개의 애니메이션을 생성해도, 각각의 타이머를 생성하는것이 아닌, 내부의 동일한 타이머를 참조하여 불필요한 메모리를 사용하지 않는다.

 

 

 

실제로 애니메이션을 구현해보면 그 차이를 알 수 있다. 아래에서 한 번 확인해보자.

 

 

 


 

 

requestAnimationFrame 함수 예시

 

 

 

한번 아래와 같이 1초 동안 콘솔에 계속 로그를 남겨보라고 하면 어떻게 될까?

 

 

let now = new Date();
let after1sec = new Date(now.getTime() + 1000);

function printTime() {
	let now = new Date();
	if (after1sec.getTime() - now.getTime() > 0) {
		console.log('record!');
		printTime();
	}
}
printTime();

 

 

바로 스택 오버플로우

 

 

당연하게도 지나치게 많은 콜스택이 쌓여 에러가 난다.

 

 

 

 

그렇다면 이번엔 requestAnimationFrame을 이용하여 똑같이 1초동안 콘솔을 찍어보면 어떤 결과가 나올까?

 

 

let startTime = null;

function printTime(timestamp) {
	if (!startTime) startTime = timestamp;
	console.log('record!');
	if (timestamp - startTime < 1000) {
		requestAnimationFrame(printTime);
	}
}
requestAnimationFrame(printTime);

 

 

음...60번이 아니라 61번이 찍히긴 했지만

 

아까 위의 코드처럼 무제한 호출되는 것이 아닌, 1초에 60번의 호출만 발생하게 된다.

 

 

즉, 1/60으로 애니메이션이 제한되어 최적화된 속도로 부드러운 애니메이션을 표현하면서 성능은 최대한 확보할 수 있게 된 것이다.

 

 

 

 

 

 

 

cancelAnimationFrame()

 

setInterval 함수가 clearInterval 함수로 끝낼 수 있다면,

requestAnimationFrame 함수는 cancelAnimationFrame 함수로 종료시킬 수 있다.

 

 

사용 방법은 clearInterval 함수와 똑같다.

 

 

let requestId;

function draw() {
	// 애니메이션 그리기
	requestId = requestAnimationFrame(draw);
}

// 애니메이션 시작
requestId = requestAnimationFrame(draw);

// 5초 후 애니메이션 중지
setTimeout(() => {
	cancelAnimationFrame(requestId);
}, 5000);

 

 

requestAnimationFrame 함수를 변수에 할당하고

 

 

cancelAnimationFrame 함수를 실행 할 때 해당 변수를 인수로 건네주면 종료된다.

반응형