├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── license ├── package.json ├── rafps.d.ts ├── readme.md ├── src └── index.js └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,yml,md}] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Node.js v${{ matrix.nodejs }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | nodejs: [8, 10, 12] 12 | steps: 13 | - uses: actions/checkout@master 14 | with: 15 | fetch-depth: 1 16 | 17 | - uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.nodejs }} 20 | 21 | - name: Install 22 | run: | 23 | npm install 24 | npm install -g nyc 25 | 26 | - name: Test w/ Coverage 27 | run: nyc --include=src npm test 28 | 29 | - name: Report 30 | if: matrix.nodejs >= 12 31 | run: | 32 | nyc report --reporter=text-lcov > coverage.lcov 33 | bash <(curl -s https://codecov.io/bash) 34 | env: 35 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.lock 4 | *.log 5 | dist 6 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Luke Edwards (lukeed.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rafps", 3 | "version": "1.0.0", 4 | "repository": "lukeed/rafps", 5 | "description": "A tiny (178B) helper for playing, pausing, and setting requestAnimationFrame frame rates", 6 | "module": "dist/index.mjs", 7 | "unpkg": "dist/index.min.js", 8 | "main": "dist/index.js", 9 | "types": "rafps.d.ts", 10 | "license": "MIT", 11 | "author": { 12 | "name": "Luke Edwards", 13 | "email": "luke.edwards05@gmail.com", 14 | "url": "https://lukeed.com" 15 | }, 16 | "files": [ 17 | "*.d.ts", 18 | "dist" 19 | ], 20 | "engines": { 21 | "node": ">= 6" 22 | }, 23 | "scripts": { 24 | "build": "bundt", 25 | "pretest": "npm run build", 26 | "test": "tape -r esm test/*.js | tap-spec" 27 | }, 28 | "keywords": [ 29 | "fps", 30 | "frame rate", 31 | "cancelAnimationFrame", 32 | "requestAnimationFrame", 33 | "raf" 34 | ], 35 | "devDependencies": { 36 | "bundt": "0.4.0", 37 | "esm": "3.2.25", 38 | "tap-spec": "5.0.0", 39 | "tape": "4.13.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rafps.d.ts: -------------------------------------------------------------------------------- 1 | export interface RAFPS { 2 | play(): void; 3 | pause(): void; 4 | } 5 | 6 | export type Callback = (frame: number) => unknown; 7 | 8 | export default function(callback: Callback, fps?: number): RAFPS; 9 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # rafps [![codecov](https://badgen.now.sh/codecov/c/github/lukeed/rafps)](https://codecov.io/gh/lukeed/rafps) 2 | 3 | > A tiny (178B) helper for playing, pausing, and setting [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) frame rates. 4 | 5 | This module is delivered as: 6 | 7 | * **ES Module**: [`dist/rafps.mjs`](https://unpkg.com/rafps/dist/index.mjs) 8 | * **CommonJS**: [`dist/rafps.js`](https://unpkg.com/rafps/dist/index.js) 9 | * **UMD**: [`dist/rafps.min.js`](https://unpkg.com/rafps) 10 | 11 | ## Install 12 | 13 | ``` 14 | $ npm install --save rafps 15 | ``` 16 | 17 | 18 | ## Usage 19 | 20 | ```js 21 | import rafps from 'rafps'; 22 | 23 | // Run at 30fps 24 | const ctx = rafps(frame => { 25 | console.log('Current frame:', frame); 26 | 27 | // Do animation things... 28 | 29 | // target hit, pause animation 30 | // .. or can run indefinitely 31 | if (frame >= 1e3) ctx.pause(); 32 | }, 30); 33 | 34 | // Begin playback 35 | ctx.play(); 36 | ``` 37 | 38 | 39 | ## API 40 | 41 | ### rafps(draw, fps?) 42 | Returns: `RAFPS` 43 | 44 | Returns a new `RAFPS` instance which can toggle between play/pause states using the same `draw` and `fps` values. 45 | 46 | #### draw 47 | 48 | Type: `Function`
49 | Required: `true` 50 | 51 | The callback function to invoke at every "frame" tick. 52 | 53 | It will receive one argument, `frame` (number), which is the current frame count since playback began.
The `frame` count starts at `0` and will increment indefinitely until paused. Once paused, `frame` will start from `0` the next time playback begins. 54 | 55 | #### fps 56 | Type: `Number`
57 | Default: `60` 58 | 59 | The number of _frames per second_* to target.
60 | Put differently, this is the maximum number of times `draw` will be called per second. 61 | 62 | > **Important:** This is a time-based calculation, so it's a **best guess** solution!
Also, your true frame rate will be at the mercy of your `draw` callback.
You should aim for lightweight operations that complete within `1ms`. 63 | 64 | 65 | ### rafps.play() 66 | Returns: `void` 67 | 68 | Begin playback.
69 | Your `draw` function will begin invocations, receiving a new `frame` (number) parameter. See [`draw`](#draw) for info. 70 | 71 | ### rafps.pause() 72 | Returns: `void` 73 | 74 | Pause playback. 75 | 76 | 77 | ## License 78 | 79 | MIT © [Luke Edwards](https://lukeed.com) 80 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export default function (draw, fps) { 2 | var delay = 1e3 / (fps || 60); 3 | var tmp, pid, last, frame = -1; 4 | 5 | function loop(time) { 6 | if (!last) last = time; 7 | tmp = ((time - last) / delay) | 0; 8 | if (pid && tmp > frame) draw(frame=tmp); 9 | if (pid) pid = requestAnimationFrame(loop); 10 | } 11 | 12 | return { 13 | play: function () { 14 | if (!pid) pid = requestAnimationFrame(loop); 15 | }, 16 | pause: function () { 17 | if (pid) { 18 | last = pid = cancelAnimationFrame(pid); 19 | frame = -1; 20 | } 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import rafiki from '../src'; 3 | 4 | // --- 5 | // @see https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 6 | 7 | var lastTime = 0; 8 | global.requestAnimationFrame = function (callback) { 9 | var currTime = Date.now(); 10 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 11 | var id = setTimeout(function () { 12 | callback(currTime + timeToCall); 13 | }, timeToCall); 14 | lastTime = currTime + timeToCall; 15 | return id; 16 | }; 17 | 18 | global.cancelAnimationFrame = function(id) { 19 | clearTimeout(id); 20 | }; 21 | 22 | // --- 23 | 24 | test('exports', t => { 25 | t.is(typeof rafiki, 'function', 'exports a function'); 26 | t.end(); 27 | }); 28 | 29 | test('instance', t => { 30 | let ctx = rafiki(console.log); 31 | t.is(typeof ctx.play, 'function'); 32 | t.is(typeof ctx.pause, 'function'); 33 | t.end(); 34 | }); 35 | 36 | test('usage', t => { 37 | t.plan(4); 38 | 39 | let num = 0; 40 | let ctx = rafiki(x => { 41 | if (num++ == 0) t.is(x, 0, 'starts w/ 0'); 42 | if (num > 9) { 43 | t.is(num, 10, 'ends w/ 10'); 44 | t.is(ctx.pause(), undefined, '~> pause returns nothing'); 45 | } 46 | }); 47 | 48 | t.is(ctx.play(), undefined, '~> play returns nothing'); 49 | }); 50 | --------------------------------------------------------------------------------