JavaScript Performance Monitor for real-time FPS and frame timing statistics
npx @tessl/cli install tessl/npm-stats-js@1.0.0stats.js is a lightweight JavaScript performance monitoring library that provides a visual widget for displaying real-time FPS (frames per second) and frame render time statistics. It creates a compact DOM-based overlay that can be embedded in web applications for performance debugging and optimization.
npm install stats.js// CommonJS
const Stats = require('stats.js');
// ES Modules (if using a bundler)
import Stats from 'stats.js';Browser direct include:
<script src="path/to/stats.min.js"></script>
<!-- Stats constructor is available globally -->// Create a new performance monitor
const stats = new Stats();
// Position the widget (usually top-left corner)
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
// Option 1: Manual measurement
function gameLoop() {
stats.begin();
// Your performance-critical code here
// (game rendering, calculations, etc.)
stats.end();
requestAnimationFrame(gameLoop);
}
// Option 2: Convenience method
function gameLoop() {
stats.update(); // Equivalent to calling end() then begin()
// Your performance-critical code here
requestAnimationFrame(gameLoop);
}
// Start the loop
requestAnimationFrame(gameLoop);stats.js is built around several key design patterns:
performance.now() with Date.now() fallback) for accurate measurementsCreates a new performance monitoring widget instance with interactive display modes.
/**
* Creates a new Stats performance monitor widget
* @constructor
* @returns {Object} Stats instance with methods and properties
*/
function Stats();The widget automatically creates a visual display with:
interface StatsInstance {
/** Library revision number (currently 13) */
REVISION: number;
/** The widget's container DOM element that can be appended to the document */
domElement: HTMLDivElement;
}Core methods for measuring code performance within a frame or time period.
/**
* Marks the beginning of a performance measurement frame
* Call before the code you want to measure
*/
begin(): void;
/**
* Marks the end of a performance measurement frame and updates display
* Call after the code you want to measure
* @returns {number} Current timestamp (from performance.now() or Date.now())
*/
end(): number;
/**
* Convenience method that calls end() then immediately begins a new measurement
* Useful for continuous monitoring in animation loops
*/
update(): void;Usage Examples:
const stats = new Stats();
document.body.appendChild(stats.domElement);
// Measuring a specific operation
stats.begin();
performExpensiveCalculation();
stats.end();
// Measuring in an animation loop
function animate() {
stats.begin();
// Render scene, update game logic, etc.
renderScene();
updatePhysics();
stats.end();
requestAnimationFrame(animate);
}
// Alternative using update() method
function animate() {
stats.update();
// Your code here runs after measurement ends and new one begins
renderScene();
updatePhysics();
requestAnimationFrame(animate);
}Control which performance metric is displayed in the widget.
/**
* Sets the display mode of the performance monitor
* @param {number} value - Display mode (0 = FPS mode, 1 = MS mode)
*/
setMode(value: number): void;Usage Examples:
const stats = new Stats();
// Start in FPS mode (default)
stats.setMode(0);
// Switch to milliseconds mode
stats.setMode(1);
// Toggle between modes programmatically
let currentMode = 0;
function toggleMode() {
currentMode = (currentMode + 1) % 2;
stats.setMode(currentMode);
}The widget can be customized using CSS by targeting specific element IDs:
/* Main container */
#stats {
width: 80px;
opacity: 0.9;
cursor: pointer;
}
/* FPS mode styling */
#fps { /* Container for FPS display */ }
#fpsText { /* FPS text label */ }
#fpsGraph { /* FPS graph container */ }
/* MS mode styling */
#ms { /* Container for MS display */ }
#msText { /* MS text label */ }
#msGraph { /* MS graph container */ }performance.now() when available, falls back to Date.now()document.createElement and basic DOM manipulationmodule.exports) and global variable patternsconst stats = new Stats();
stats.domElement.style.position = 'fixed';
stats.domElement.style.top = '0';
stats.domElement.style.right = '0';
stats.domElement.style.zIndex = '10000';
document.body.appendChild(stats.domElement);
function gameLoop(timestamp) {
stats.begin();
updateGame(timestamp);
renderGame();
stats.end();
requestAnimationFrame(gameLoop);
}// Monitor specific operations
function monitoredFunction() {
stats.begin();
try {
// Heavy computation or rendering
processLargeDataSet();
updateUI();
} finally {
stats.end(); // Ensure measurement ends even if errors occur
}
}
// Monitor animation performance
function smoothAnimation() {
stats.update();
// Animation code here
animateElements();
requestAnimationFrame(smoothAnimation);
}// Only show stats in development
if (process.env.NODE_ENV === 'development') {
const stats = new Stats();
stats.setMode(1); // Start with MS mode for development
document.body.appendChild(stats.domElement);
// Make it draggable or add toggle button
stats.domElement.style.position = 'fixed';
stats.domElement.style.top = '10px';
stats.domElement.style.left = '10px';
stats.domElement.style.zIndex = '9999';
}