부드럽고 성능이 뛰어난 애니메이션은 최신 웹 애플리케이션에 필수적입니다. 그러나 부적절하게 관리하면 브라우저의 메인 스레드에 과부하가 걸려 성능이 저하되고 애니메이션이 버벅거릴 수 있습니다. rAF(requestAnimationFrame)는 디스플레이의 새로 고침 빈도와 애니메이션을 동기화하도록 설계된 브라우저 API로, setTimeout과 같은 대안에 비해 더 부드러운 동작을 보장합니다. 그러나 rAF를 효율적으로 사용하려면 특히 여러 애니메이션을 처리할 때 신중한 계획이 필요합니다.
이 글에서는 애니메이션 관리를 중앙 집중화하고, FPS 제어를 도입하고, 브라우저의 메인 스레드의 응답성을 유지하여 requestAnimationFrame을 최적화하는 방법을 살펴보겠습니다.
초당 프레임 수(FPS)는 애니메이션 성능을 논의할 때 매우 중요합니다. 대부분의 화면은 60FPS로 새로 고쳐집니다. 이는 requestAnimationFrame이 초당 60번 호출된다는 의미입니다. 부드러운 애니메이션을 유지하려면 브라우저는 프레임당 약 16.67밀리초 내에 작업을 완료해야 합니다.
단일 프레임 동안 너무 많은 작업이 실행되면 브라우저가 대상 프레임 시간을 놓치게 되어 끊김 현상이 발생하거나 프레임이 삭제될 수 있습니다. 특정 애니메이션의 FPS를 낮추면 메인 스레드의 부하를 줄여 성능과 부드러움 사이의 균형을 맞추는 데 도움이 될 수 있습니다.
애니메이션을 보다 효율적으로 관리하기 위해 여러 requestAnimationFrame 호출을 코드 전체에 분산시키는 대신 공유 루프를 사용하여 애니메이션 처리를 중앙 집중화할 수 있습니다. 중앙 집중식 접근 방식을 통해 중복 호출을 최소화하고 FPS 제어를 더 쉽게 추가할 수 있습니다.
다음 AnimationManager 클래스를 사용하면 대상 FPS를 제어하면서 애니메이션 작업을 등록 및 등록 취소할 수 있습니다. 기본적으로 60FPS를 목표로 하지만 성능 요구에 따라 조정될 수 있습니다.
class AnimationManager { private tasks: Set= new Set(); private fps: number = 60; // Target FPS private lastFrameTime: number = performance.now(); private animationId: number | null = null; // Store the animation frame ID private run = (currentTime: number) => { const deltaTime = currentTime - this.lastFrameTime; // Ensure the tasks only run if enough time has passed to meet the target FPS if (deltaTime > 1000 / this.fps) { this.tasks.forEach((task) => task(currentTime)); this.lastFrameTime = currentTime; } this.animationId = requestAnimationFrame(this.run); }; public registerTask(task: FrameRequestCallback) { this.tasks.add(task); if (this.tasks.size === 1) { this.animationId = requestAnimationFrame(this.run); // Start the loop if this is the first task } } public unregisterTask(task: FrameRequestCallback) { this.tasks.delete(task); if (this.tasks.size === 0 && this.animationId !== null) { cancelAnimationFrame(this.animationId); // Stop the loop if no tasks remain this.animationId = null; // Reset the ID } } } export const animationManager = new AnimationManager();
이 설정에서는 프레임 간 deltaTime을 계산하여 대상 FPS를 기반으로 다음 업데이트에 충분한 시간이 경과했는지 확인합니다. 이를 통해 브라우저의 메인 스레드가 과부하되지 않도록 업데이트 빈도를 조절할 수 있습니다.
각각 다른 애니메이션을 사용하는 세 개의 상자에 애니메이션을 적용하는 예를 만들어 보겠습니다. 하나는 크기가 조정되고, 다른 하나는 색상이 변경되고, 세 번째 상자는 회전합니다.
HTML은 다음과 같습니다.
CSS는 다음과 같습니다.
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
이제 JavaScript를 추가하여 각 상자에 다른 속성을 적용해 보겠습니다. 하나는 크기를 조정하고, 다른 하나는 색상을 변경하고, 세 번째는 회전합니다.
1단계: 선형 보간 추가(lerp)
선형 보간(lerp)은 애니메이션에서 두 값 사이를 원활하게 전환하는 데 사용되는 일반적인 기술입니다. 점진적이고 부드러운 진행을 만드는 데 도움이 되므로 시간이 지남에 따라 속성을 확장, 이동 또는 변경하는 데 이상적입니다. 이 함수는 시작 값, 종료 값, 전환이 얼마나 진행되는지 결정하는 정규화된 시간(t)의 세 가지 매개변수를 사용합니다.
function lerp(start: number, end: number, t: number): number { return start (end - start) * t; }
2단계: 애니메이션 크기 조정
첫 번째 상자의 크기 조정에 애니메이션을 적용하는 함수를 만드는 것부터 시작합니다.
function animateScale( scaleBox: HTMLDivElement, startScale: number, endScale: number, speed: number ) { let scaleT = 0; function scale() { scaleT = speed; if (scaleT > 1) scaleT = 1; const currentScale = lerp(startScale, endScale, scaleT); scaleBox.style.transform = `scale(${currentScale})`; if (scaleT === 1) { animationManager.unregisterTask(scale); } } animationManager.registerTask(scale); }
3단계: 컬러 애니메이션
다음으로 두 번째 상자의 색상 변경에 애니메이션을 적용합니다.
function animateColor( colorBox: HTMLDivElement, startColor: number, endColor: number, speed: number ) { let colorT = 0; function color() { colorT = speed; if (colorT > 1) colorT = 1; const currentColor = Math.floor(lerp(startColor, endColor, colorT)); colorBox.style.backgroundColor = `rgb(${currentColor}, 100, 100)`; if (colorT === 1) { animationManager.unregisterTask(color); } } animationManager.registerTask(color); }
4단계: 회전 애니메이션
마지막으로 세 번째 상자를 회전하는 함수를 만듭니다.
function animateRotation( rotateBox: HTMLDivElement, startRotation: number, endRotation: number, speed: number ) { let rotationT = 0; function rotate() { rotationT = speed; // Increment progress if (rotationT > 1) rotationT = 1; const currentRotation = lerp(startRotation, endRotation, rotationT); rotateBox.style.transform = `rotate(${currentRotation}deg)`; // Unregister task once the animation completes if (rotationT === 1) { animationManager.unregisterTask(rotate); } } animationManager.registerTask(rotate); }
5단계: 애니메이션 시작
마지막으로 세 상자 모두에 대해 애니메이션을 시작할 수 있습니다.
// Selecting the elements const scaleBox = document.querySelector("#animate-box-1") as HTMLDivElement; const colorBox = document.querySelector("#animate-box-2") as HTMLDivElement; const rotateBox = document.querySelector("#animate-box-3") as HTMLDivElement; // Starting the animations animateScale(scaleBox, 1, 1.5, 0.02); // Scaling animation animateColor(colorBox, 0, 255, 0.01); // Color change animation animateRotation(rotateBox, 360, 1, 0.005); // Rotation animation
requestAnimationFrame을 사용할 때 애니메이션이 브라우저의 메인 스레드에서 실행된다는 점을 명심하는 것이 중요합니다. 작업이 너무 많은 기본 스레드에 과부하가 걸리면 브라우저가 애니메이션 프레임을 놓치고 끊김 현상이 발생할 수 있습니다. 이것이 바로 중앙 집중식 애니메이션 관리자 및 FPS 제어와 같은 도구를 사용하여 애니메이션을 최적화하면 여러 애니메이션에서도 부드러움을 유지하는 데 도움이 될 수 있는 이유입니다.
JavaScript에서 애니메이션을 효율적으로 관리하려면 requestAnimationFrame을 사용하는 것 이상이 필요합니다. 애니메이션을 중앙 집중화하고 FPS를 제어하면 메인 스레드의 응답성을 유지하면서 더 부드럽고 성능이 뛰어난 애니메이션을 보장할 수 있습니다. 이 예에서는 단일 AnimationManager로 여러 애니메이션을 처리하는 방법을 보여주면서 성능과 유용성을 모두 최적화하는 방법을 보여주었습니다. 단순성을 위해 일관된 FPS를 유지하는 데 중점을 두었지만 이 접근 방식을 확장하여 다양한 애니메이션에 대해 다양한 FPS 값을 처리할 수 있습니다. 하지만 이는 이 문서의 범위를 벗어났습니다.
Github 저장소: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3