MainLoop.js gives a JavaScript game a frame-rate independent update loop
Provides a well-constructed main loop useful for JavaScript games and other animated or time-dependent applications.
At a glance
- What is it?
- MainLoop.js is a small dependency free JavaScript library that runs your begin, update, draw and end callbacks every frame, with the update step receiving a delta in milliseconds so physics and movement stay correct at any frame rate. It suits browser game and animation projects, and it is a single loop rather than a full engine.
- Who is it for?
- Adopt MainLoop.js if you are writing a browser game or an animated canvas application and you want the update step to be frame rate independent without hand rolling requestAnimationFrame timing. Do not adopt it if you need a full engine with sprites, physics, input and scene management, because MainLoop.js supplies the loop and nothing else, and the alternative in that case is a full engine rather than a different loop library.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 100 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: loops that speed up and slow down with the frame rate
MainLoop.js describes itself as providing a well-constructed main loop useful for JavaScript games and other animated or time-dependent applications, and the README makes the case for it in unusually blunt terms. The main loop is a core part of any application in which state changes over time, and in games it is typically responsible for computing physics and AI as well as drawing the result on the screen. Main loops are difficult to write correctly due to timing issues, and the README states that the vast majority of main loops found online are written incorrectly, resulting in applications that speed up or slow down depending on the frame rate. The consequences it names are concrete, characters running through walls or being unable to jump over obstacles, and it adds that such loops can also produce applications that are non-deterministic. The project's author, Isaac Sukin, wrote the widely read explanation of JavaScript game loops and timing that the README links to as a detailed walkthrough of what problems this project solves, so the reasoning behind the library is documented separately from the library itself.
Four callbacks and the delta that fixes the timing
The mechanism is four optional functions. MainLoop works by running the functions you define every time the browser is ready to update the screen, up to about 60 times per second on most monitors. MainLoop.setBegin() installs the begin function, which runs at the beginning of each frame and is typically used to process input. MainLoop.setUpdate() installs the update function, which runs zero or more times per frame depending on the frame rate and is used to compute anything affected by time, typically physics and AI movements. MainLoop.setDraw() installs the draw function, which should update the screen, usually by changing the DOM or painting a canvas. MainLoop.setEnd() installs the end function, which runs at the end of each frame and is typically used for cleanup tasks such as adjusting the visual quality based on the frame rate. The update function receives a delta parameter holding the amount of time in milliseconds that should be simulated, and that value should be used to calculate movement. The README's example is an object with an x-axis velocity of 100 units per second.
function update(delta) {
obj.x += 0.1 * delta;
}Because the movement is expressed as a rate multiplied by elapsed time, the README states that this structure will ensure that your application behaves regardless of the frame rate. That single idea is the reason the library exists.
Installing it from npm, Bower, a CDN or a script tag
You can download the script normally, install it with npm, or install it with Bower, and the README gives both package managers. For a build tool project the npm route is the one to use.
npm install mainloop.jsFor Bower the command is bower install mainloop. Including the script on a page without a module loader takes a script tag pointing at the built file, and the README lists the four possible locations, from a direct download or git clone, to the npm install directory, to the Bower directory, to a CDN URL on cdn.jsdelivr.net at the @latest tag. Once the script is on the page you have access to the MainLoop global. The package.json confirms the entry point is build/mainloop.min.js, and the repository carries a build/ directory with the built artefacts, a src/ directory with the source, a demo/ directory with a runnable example, and a docs/ directory. The README also points to the minified source file itself and notes it is heavily documented, with the source about 75 percent documentation, which is unusual for a library of this size and is where the timing details live.
Module loaders, CommonJS, AMD and TypeScript
MainLoop.js is compatible with CommonJS, for example with node.js or browserify, and with AMD, for example with RequireJS. The README is explicit about the mechanics: if you are using a module loader or want to use MainLoop server-side you can call require('mainloop.js') to get the MainLoop object, or include 'mainloop.js' in the dependencies you pass to a define() call. For TypeScript users there are typings available as a separate package, and the README gives the command to install them.
npm install --save-dev @types/mainloop.jsThe typings are maintained outside this repository, which is worth noting for anyone planning to rely on them. For Svelte users the README suggests trying svelte-mainloop, a wrapper that adds several conveniences, again maintained separately. The Notes section is short and gives the compatibility envelope: compatible with all modern browsers including IE9 and mobile browsers, as well as node.js, with no dependencies, and the library is under 1KB minified and gzipped. That last figure explains why the library is a reasonable default choice for a small project, since a loop implementation of a few hundred bytes costs nothing to include.
Starting, stopping and the shape of the usage example
To start the application you call MainLoop.start(), and the setters chain, so a minimal start looks like this.
MainLoop.setUpdate(update).setDraw(draw).start();You can call MainLoop.stop() to stop the application. The chain in that example is the whole API surface a small game needs: an update function that advances state by delta, a draw function that paints, and start. The optional callbacks mean you can install only what you need, which for a simple animation might be just draw. The README links both the API documentation and a usage example, and the example source is in the demo directory of the repository as an index.html file, so you can read a complete page rather than a fragment. The API documentation is hosted at icecreamyou.github.io/MainLoop.js/docs, generated with the jsduck configuration file in the repository, and the docs/ directory in the tree holds the source for that documentation.
Contributing: grunt, uglify and the style checks
Contributions are welcome, and the README gives the exact workflow. To get started you run npm install in the project's directory, then run grunt before submitting a pull request.
npm installThe grunt run updates the minified script and the docs as well as performing a style check, so a pull request that does not include the regenerated build output is incomplete rather than merely untidy. The package.json lists the toolchain behind that: grunt itself at version 1.4.1 or newer, grunt-contrib-jshint for linting, grunt-contrib-uglify for the minified build, grunt-contrib-watch, grunt-exec and grunt-jscs for the style check. The Gruntfile.js at the root of the repository defines those tasks, and the top level also holds .jscs.json and .jsduck.json, the style and documentation configuration. The CI badge points at Travis CI on the gh-pages branch, which is also the default branch, so the branch layout and the build pipeline are older than the current GitHub Actions defaults that most projects use.
Where MainLoop.js is the wrong choice
The limits come from the size of the thing. The first is that MainLoop.js is a loop and nothing more. It does not draw, it does not load assets, it does not handle input, it does not manage scenes and it does not do collision detection, so a project that needs those has to build them or take them from elsewhere, and at that point an engine is usually less total work than the collection of libraries MainLoop.js would sit alongside. The second is the age of the project. The latest npm version is 1.0.4, released in 2017-05-30, with 1.0.3 in March 2016 and 1.0.2 in February 2016, and the last push to the repository was on 2026-07-15, so the code is stable but the published package has not moved in years, which means the tooling it ships with, grunt and jshint and jscs, is also from that era. The third is the release channel, where a 1.0.x version number gives no signal about how a breaking change would be communicated if one happened, and there is no changelog in the repository tree to check. The fourth is the timing model itself: the update function runs zero or more times per frame depending on the frame rate, which is exactly what makes movement correct, but it also means update is not a once per frame place to put work that must happen once, such as advancing a cutscene timer or decrementing a retry counter. The fifth is that the loop is driven by the browser being ready to update the screen, so in a background tab the behaviour differs from a foreground tab and the library does not document how it handles that. A reasonable alternative for a project that only needs a correct update step is to use requestAnimationFrame with your own timestamp bookkeeping, which is a few dozen lines, or to use a framework with its own loop, such as Phaser or Three.js with a loop integration, where the loop is one part of a larger system rather than the whole system.
Editorial conclusion
Adopt MainLoop.js if you are writing a browser game or an animated canvas application and you want the update step to be frame rate independent without hand rolling requestAnimationFrame timing. Do not adopt it if you need a full engine with sprites, physics, input and scene management, because MainLoop.js supplies the loop and nothing else, and the alternative in that case is a full engine rather than a different loop library. Verify two things before you commit: that your existing code is not already driving requestAnimationFrame itself, since two loops will fight over frames, and that you understand the update function can run zero or more times per frame, which means side effects belong in draw or begin rather than in update. The practical first step is to install the package with npm install mainloop.js, set your update and draw functions with MainLoop.setUpdate(update).setDraw(draw), and call MainLoop.start() in a page where you can watch the console frame timing before adding anything else.
Frequently asked questions
What is MainLoop.js?
It is a dependency free JavaScript library that provides a well-constructed main loop for games and other animated or time-dependent applications. It is MIT licensed, under 1KB minified and gzipped, and compatible with modern browsers including IE9 as well as node.js.
How do I install MainLoop.js?
Run npm install mainloop.js, or bower install mainloop. Without a module loader you include the built file with a script tag pointing at build/mainloop.min.js, either from a direct download, from node_modules, from bower_components, or from a CDN, and you then have access to the MainLoop global.
What are the four MainLoop.js callbacks?
MainLoop.setBegin() installs a function that runs at the beginning of each frame, typically to process input. MainLoop.setUpdate() installs one that runs zero or more times per frame and computes anything affected by time. MainLoop.setDraw() installs one that updates the screen, and MainLoop.setEnd() installs one that runs at the end of a frame for cleanup such as adjusting visual quality based on the frame rate.
How does MainLoop.js keep movement correct at different frame rates?
The update function receives a delta parameter holding the amount of time in milliseconds to simulate, and you multiply your velocity by that delta, for example obj.x += 0.1 * delta for a velocity of 100 units per second. Because movement is expressed as a rate times elapsed time, the README states the application behaves regardless of the frame rate.
How do I contribute to MainLoop.js?
Run npm install in the project directory, then run grunt before submitting a pull request. The grunt run updates the minified script and the docs and performs a style check, and the build toolchain in package.json is grunt with the jshint, uglify, watch, exec and jscs plugins.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/icecreamyou-mainloop-js)Was this useful?