Skip to content

Instantly share code, notes, and snippets.

@rsms
Created November 3, 2019 20:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rsms/88b8064cc31a9b6bf869293f90e70fd0 to your computer and use it in GitHub Desktop.
Save rsms/88b8064cc31a9b6bf869293f90e70fd0 to your computer and use it in GitHub Desktop.
FPS measurement in web browser
// measureFPS uses requestAnimationFrame to measure frame times and reports
// the average frames per second.
//
function measureFPS(report :(fps:number)=>void) {
const samplesSize = 120 // total number of frame times look at (window size)
const samples :number[] = [] // ring buffer; sliding window
const reportAt = Math.round(samplesSize / 4)
let samplesIndex = 0 // next index in samples
let prevTime = 0 // last time value; frameTime = prevTime - time
const maxMissedReports = 2
const reportTimeMissThreshold = (reportAt/60) * maxMissedReports * 1000
let lastReportTime = 0
// When a tab goes idle, this function is not called for a while.
// Then when the tab goes active, it's called with a huge time delta, pulling down the
// FPS considerably. To counter for this, we record the real time when we report.
// Before we make a report, we look to see if we missed more than maxMissedReports reports,
// and if so, we reset and start over.
const sample = (time :number) => {
samples[samplesIndex++] = time - prevTime
prevTime = time
if (samples.length == samplesSize) {
if (samplesIndex == samplesSize) {
samplesIndex = 0
}
if (samplesIndex % reportAt == 0) {
// report
let now = Date.now()
if (lastReportTime != 0 && now - lastReportTime > reportTimeMissThreshold) {
// tab went idle for a while and missed some reports. Reset.
samples.length = 0
lastReportTime = 0
samplesIndex = 0
} else {
lastReportTime = now
let avgFrameTime = samples.reduce((v, a) => a + v) / samplesSize
report(1000/avgFrameTime)
}
}
}
requestAnimationFrame(sample)
}
requestAnimationFrame(time => {
prevTime = time
requestAnimationFrame(sample)
})
}
// Example
let fpsEl = document.createElement("div")
document.body.appendChild(fpsEl)
fpsEl.innerText = "∞ FPS"
measureFPS(fps => {
fpsEl.innerText = (fps > 0 ? fps.toFixed(0) : parseFloat(fps.toFixed(2))) + " FPS"
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment