모바일 환경에서 Animation 프레임 드랍 해결하기
2023-05-04에 수정한 Animation을 deploy해서 모바일 환경에서 구동해보았는데, 데스크탑 환경에서 보던 부드러움이 나오지 않는다. 보기 거북할 정도로 프레임 드랍이 일어나, 문제가 뭘까 생각해보고 해결해보려 한다.
new Date()
쓰지 마세요?
위 문제를 해결하기 위해 Stack Overflow를 뒤져보던 도중, 다음과 같은 글을 발견했다. Calculate FPS in Canvas using requestAnimationFrame
기존 코드에서 현재 FPS를 계산하기 위해서 new Date()
를 사용하고 있는데, Date
API는 인터벌을 측정하는 데에 적합하지 않다는 것이다.
The Date-API uses the operating system's internal clock, which is constantly updated and synchronized with NTP time servers. This means, that the speed / frezquency of this clock is sometimes faster and sometimes slower than the actual time - and therefore not useable for measuring durations and framerates.
If someone changes the system time (either manually or due to DST), you could at least see the problem if a single frame suddenly needed an hour. Or a negative time. But if the system clock ticks 20% faster to synchronize with world-time, it is practically impossible to detect.
Also, the Date-API is very imprecise - often much less than 1ms. This makes it especially useless for framerate measurements, where one 60Hz frame needs ~17ms.
위와 같은 이유 때문에, performance.now()
를 사용하는 것이 좋다고 한다.
그래서, new Date()
로 시간을 측정하던 부분을 performance.now()
로 모두 리팩토링하였고, 작성된 소스코드는 다음과 같다.
const refAngle = useRef<number>(-0.4)
const [angle, setAngle] = useState<number>(-0.4)
var then: number
var startTime: number
var frameCount: number = 0
const animate = () => {
if (refAngle.current > 1) return
var now: number = performance.now()
var sinceStart = now - startTime
var currentFps = Math.round((1000 / (sinceStart / ++frameCount)) * 100) / 100
refAngle.current += 1.25 / currentFps
setAngle(refAngle.current)
requestAnimationFrame(animate)
}
useEffect(() => {
then = performance.now()
startTime = then
var animationFrame = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(animationFrame)
}
}, [])
그런데, 위 글에 딸린 Comment들을 읽던 도중 다음과 같은 글을 발견했다.
Don't use the
Date
, but don't even useperformance.now
.requestAnimationFrame
passes an highResTimestamp to its callback
requestAnimationFrame()
이 콜백으로 주는 타임스탬프를 이용하는 것이 더 낫다는 것인데, 이 방법으로도 구현해보고 성능을 테스트 해볼 예정이다.
결과
위와 같이 new Date()
를 performance.now()
로 변경하는 것 만으로도 모바일 환경에서 프레임 드랍이 일어나던 문제가 어느정도 해결되었다. 그럼에도 불구하고, 60fps가 나오지는 않아서 눈으로 보기에는 여전히 부드럽지는 않은데, 이 부분은 또 어디가 문제인지 다시 확인해볼 생각이다.
관련 문제를 찾아보던 도중, iOS 자체에서 requestAnimationFrame()
을 30fps로 Throttle을 건다는 글을 발견했다. 이와 관련된 문제인지 다시 한 번 살펴봐야겠다.