Chrome 88 (2021년 1월)에서는 특정 조건에서 숨겨진 페이지의 체이닝된 JavaScript 타이머를 크게 제한합니다. 이렇게 하면 CPU 사용량이 줄어 배터리 사용량도 줄어듭니다. 이로 인해 동작이 변경되는 일부 특수한 경우가 있지만, 타이머는 다른 API가 더 효율적이고 안정적인 경우에 자주 사용됩니다.
전문 용어가 많고 약간 모호했습니다. 자세히 살펴보겠습니다.
용어
숨겨진 페이지
일반적으로 숨김은 다른 탭이 활성 상태이거나 창이 최소화되었음을 의미하지만 브라우저는 콘텐츠가 완전히 표시되지 않을 때마다 페이지를 숨김으로 간주할 수 있습니다. 일부 브라우저는 다른 브라우저보다 더 나아가지만 항상 페이지 가시성 API를 사용하여 브라우저가 가시성이 변경되었다고 생각하는 시점을 추적할 수 있습니다.
JavaScript 타이머
JavaScript 타이머는 나중에 콜백을 예약할 수 있는 setTimeout
및 setInterval
를 의미합니다. 타이머는 유용하며 사라지지 않지만 이벤트가 더 효율적이고 정확할 때 상태를 폴링하는 데 사용되는 경우가 있습니다.
체이닝된 타이머
setTimeout
콜백과 동일한 태스크에서 setTimeout
를 호출하면 두 번째 호출이 '연쇄'됩니다. setInterval
을 사용하면 각 반복이 체이닝의 일부가 됩니다. 코드를 사용하면 더 쉽게 이해할 수 있습니다.
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
추가 조건:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
제한 작동 방식
제한은 단계적으로 적용됩니다.
최소 제한
다음 중 하나라도 참인 경우 예약된 타이머에 이 문제가 발생합니다.
- 페이지가 표시됩니다.
- 지난 30초 동안 페이지에서 소리가 났습니다. 이는 어떤 소리 API에서든 발생할 수 있지만 소리가 없는 오디오 트랙은 집계되지 않습니다.
요청된 제한 시간이 4밀리초 미만이고 체인 수가 5개 이상인 경우 제한 시간은 4밀리초로 설정되며, 이 경우 타이머는 제한되지 않습니다. 이는 새로운 것이 아닙니다. 브라우저는 오랫동안 이를 실행해 왔습니다.
제한
이는 최소 제한이 적용되지 않고 다음 중 하나라도 true인 경우 예약된 타이머에 발생합니다.
- 체인 개수가 5개 미만입니다.
- 페이지가 5분 미만 동안 숨겨져 있었습니다.
- WebRTC가 사용 중입니다. 특히 'open'
RTCDataChannel
또는 'live'MediaStreamTrack
가 있는RTCPeerConnection
가 있습니다.
브라우저는 이 그룹의 타이머를 초당 한 번씩 확인합니다. 1초에 한 번만 확인되므로 유사한 시간 제한이 있는 타이머가 일괄 처리되어 탭에서 코드를 실행하는 데 필요한 시간이 통합됩니다. 이 역시 새로운 것이 아닙니다. 브라우저는 수년 동안 어느 정도 이러한 작업을 해 왔습니다.
강렬한 제한
Chrome 88의 새로운 기능을 소개합니다. 집중적인 제한은 최소 제한 또는 제한 조건이 적용되지 않고 다음 조건이 모두 true인 경우 예약된 타이머에 발생합니다.
- 페이지가 5분 넘게 숨겨져 있습니다.
- 체인 수가 5 이상입니다.
- 페이지가 30초 이상 조용했습니다.
- WebRTC가 사용되지 않습니다.
이 경우 브라우저는 이 그룹의 타이머를 분당 한 번 확인합니다. 이전과 마찬가지로 타이머가 이러한 분 단위 확인에서 일괄 처리됩니다.
해결 방법
일반적으로 타이머보다 나은 대안이 있거나 타이머를 다른 것과 결합하여 CPU와 배터리 수명을 개선할 수 있습니다.
상태 폴링
이는 타이머를 가장 일반적으로 (오)용하는 방법으로, 타이머를 계속 확인하거나 폴링하여 변경사항이 있는지 확인하는 데 사용됩니다. 대부분의 경우 이에 상응하는 푸시가 있습니다. 여기서는 변경사항이 발생하면 항목이 변경사항을 알려주므로 계속 확인할 필요가 없습니다. 동일한 작업을 실행하는 이벤트가 있는지 확인합니다.
예를 들면 다음과 같습니다.
- 요소가 표시 영역에 들어오는 시점을 알아야 하는 경우
IntersectionObserver
을 사용하세요. - 요소의 크기가 변경되는 시점을 알아야 하는 경우
ResizeObserver
를 사용하세요. - DOM이 변경되는 시점을 알아야 하는 경우
MutationObserver
또는 맞춤 요소 수명 주기 콜백을 사용하세요. - 서버를 폴링하는 대신 웹 소켓, 서버에서 전송된 이벤트, 푸시 메시지 또는 스트림 가져오기를 고려하세요.
- 오디오/동영상의 스테이지 변경사항에 반응해야 하는 경우
timeupdate
및ended
과 같은 이벤트를 사용하거나 각 프레임으로 작업해야 하는 경우requestVideoFrameCallback
를 사용하세요.
특정 시간에 알림을 표시하려면 알림 트리거도 있습니다.
애니메이션
애니메이션은 시각적인 것이므로 페이지가 숨겨져 있을 때는 CPU 시간을 사용해서는 안 됩니다.
requestAnimationFrame
는 JavaScript 타이머보다 애니메이션 작업을 예약하는 데 훨씬 뛰어납니다. 기기의 새로고침 빈도와 동기화되므로 표시 가능한 프레임당 하나의 콜백만 수신되고 해당 프레임을 생성하는 데 최대 시간이 소요됩니다. 또한 requestAnimationFrame
는 페이지가 표시될 때까지 기다리므로 페이지가 숨겨져 있을 때는 CPU를 사용하지 않습니다.
전체 애니메이션을 미리 선언할 수 있는 경우 CSS 애니메이션 또는 웹 애니메이션 API를 사용하는 것이 좋습니다. 이러한 방법은 requestAnimationFrame
와 동일한 이점을 제공하지만 브라우저에서 자동 컴포지션과 같은 추가 최적화를 실행할 수 있으며 일반적으로 더 사용하기 쉽습니다.
애니메이션의 프레임 속도가 느린 경우 (예: 깜박이는 커서) 타이머가 여전히 가장 좋은 옵션이지만 타이머를 requestAnimationFrame
와 결합하여 두 가지 장점을 모두 누릴 수 있습니다.
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
사용:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
테스트
이 변경사항은 Chrome 88 (2021년 1월)에서 모든 Chrome 사용자에게 적용됩니다. 현재 Chrome 베타, 개발자, Canary 사용자의 50% 에게 이 기능이 사용 설정되어 있습니다. 테스트하려면 Chrome 베타, 개발자, Canary를 실행할 때 다음 명령줄 플래그를 사용하세요.
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
grace_period_seconds/10
인수를 사용하면 페이지가 숨겨진 후 5분이 아닌 10초 후에 강렬한 제한이 적용되므로 제한의 영향을 더 쉽게 확인할 수 있습니다.
앞으로
타이머는 과도한 CPU 사용의 원인이므로 웹 콘텐츠를 손상시키지 않고 타이머를 제한하는 방법과 사용 사례를 충족하기 위해 추가/변경할 수 있는 API를 계속 살펴볼 예정입니다. 개인적으로는 효율적인 저주파 애니메이션 콜백을 위해 animationInterval
의 필요성을 없애고 싶습니다. 궁금한 점이 있으면 트위터에서 문의해 주세요.
헤더 사진: Unsplash의 Heather Zabriskie