feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+186
View File
@@ -0,0 +1,186 @@
# Changelog
Framesync adheres to [Semantic Versioning](http://semver.org/).
## [6.1.2] 2022-08-15
### Update
- `tslib` and `typescript`.
## [6.1.1] 2022-08-10
### Update
- Adding `types` to `exports` field.
## [6.1.0] 2021-11-24
### Update
- Updating `tslib`.
## [6.0.1] 2021-10-22
### Fixed
- Fixing bug where `flushSync` being called from an existing process would boot keep-alive processes off the thread.
## [6.0.0] 2021-09-23
### Fixed
- Fixing `exports` and `module` in `package.json`. This will break (unsupported) direct file imports.
## [5.3.0] 2021-03-30
### Added
- Adding `flushSync` API for manual flushing of job queues.
## [5.2.3] 2021-03-19
### Fixed
- Fixing `main` entry point.
## [5.2.2] 2021-03-19
### Fixed
- Fixing `main` entry point.
## [5.2.1] 2021-03-19
### Added
- Adding `exports` to `package.json`.
### Updated
- `tslib` to latest.
## [5.2.0] 2021-03-01
### Fixed
- Unbundling ES code to facilitate code-splitting in Webpack.
## [5.1.0] 2021-02-22
### Fixed
- Adding polyfill for `performance.now` for better compatibility with Node environments.
## [5.0.0] 2021-01-01
### Changed
- Using `performance.now` to measure polyfilled elapsed time.
## [4.1.0] 2020-8-24
### Added
- `preRender` step.
## [4.0.2] 2019-02-05
### Fixed
- Fixing rescheduling keepAlive method.
## [4.0.1] 2018-09-24
### Changed
- Updated README.
## [4.0.0] 2018-09-24
### Changed
- New API.
- `onFrameUpdate(callback, immediate)` -> `sync.update(callback, keepAlive, immediate)`
- `cancelOnFrameUpdate(callback)` -> `cancelSync.update(callback)`
### Added
- `keepAlive` parameter.
## [3.1.9] 2018-05-16
### Fixed
- Fixing Rollup config.
## [3.1.8] 2018-05-13
### Added
- Rollup support for outputting ES modules and UMD bundles.
## [3.1.7] 2018-01-04
### Fixed
- Fixing illegal invocation errors.
## [3.1.6] 2018-01-04
### Changed
- Using previous frame duration as default duration (for instance between active cycles).
## [3.1.5] 2018-01-04
### Changed
- Cleaning polyfill.
## [3.1.4] 2018-01-04
### Changed
- Max permitted time elapsed is now 40ms to permit 30fps max.
- When `startRenderLoop` is fired, and the loop is not active, we set a new `currentTime`.
## [3.1.3] 2017-11-08
### Fixed
- Actually pointing to new declaration file.
## [3.1.2] 2017-11-08
### Fixed
- Pointing to new declaration file.
## [3.1.1] 2017-11-08
### Fixed
- Automatically exporting declaration file.
## [3.1.0] 2017-11-08
### Added
- Added optional `true` flag to `schedule`. This will schedule a job to run at the end of the current frame step.
## [3.0.0] 2017-08-28
- `currentFrameTimestamp` becomes `currentFrameTime` for symmetry with `currentTime`.
## [2.0.1] 2017-08-26
### Added
- Changelog and Readme.
## [2.0.0] 2017-08-26
### Added
- First publish.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright © 2019 Framer BV
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
+97
View File
@@ -0,0 +1,97 @@
# Framesync
A tiny frame scheduler for performantly batching reads, updates and renders.
Segregating actions that read and write to the DOM will avoid [layout thrashing](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing).
It's also a way of ensuring order of execution across a frame. For instance, [Framer Motion](https://github.com/framer/motion) batches animations on the `update` step, and renders on the `render` step, allowing independent animation of `transform` properties.
## Install
```bash
npm install framesync
```
## Usage
Framesync splits a frame into discrete `read`, `update`, `preRender`, `render` and `postRender` steps.
### Scheduling functions
Functions can be scheduled to different parts of the render loop with `sync`.
```javascript
import sync from 'framesync';
```
It provides four functions, one for scheduling a function to run on each part of the frame:
```javascript
sync.update(() => {});
```
### Frame data
Each function is provided data about the current frame:
```javascript
sync.update(({ delta, timestamp }) => {});
```
- `delta`: Time since last frame (in milliseconds)
- `timestamp`: Timestamp of the current frame.
This object is recycled across frames, so values should be destructured if intended to be used asynchronously.
### Keep alive
We can run a function as an ongoing process by passing `true` as the second parameter:
```javascript
let count = 0;
sync.update(() => count++, true);
```
This will keep the process running until it's actively cancelled.
### Run immediately
The third parameter, `immediate`, can be used to sync a function on the **current frame step**.
By default, Framesync will schedule functions to run the next time that frame step is fired:
```javascript
sync.update(({ timestamp }) => {
// The following function will run on the subsequent frame:
sync.update((frame) => frame.timestamp !== timestamp);
});
```
By setting `immediate` to `true`, we can add this at the end of the current step:
```javascript
sync.update(({ timestamp }) => {
// The following function will run on the **current** frame:
sync.update(
(frame) => frame.timestamp === timestamp,
false,
true
);
});
```
### Cancelling
Synced processes can be cancelled with the `cancelSync` function:
```javascript
import sync, { cancelSync } from 'framesync';
let count = 0;
const process = sync.render(() => {
count++;
if (count >= 10) cancelSync.render(process);
}, true);
```
+56
View File
@@ -0,0 +1,56 @@
function createRenderStep(runNextFrame) {
let toRun = [];
let toRunNextFrame = [];
let numToRun = 0;
let isProcessing = false;
let flushNextFrame = false;
const toKeepAlive = new WeakSet();
const step = {
schedule: (callback, keepAlive = false, immediate = false) => {
const addToCurrentFrame = immediate && isProcessing;
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
if (keepAlive)
toKeepAlive.add(callback);
if (buffer.indexOf(callback) === -1) {
buffer.push(callback);
if (addToCurrentFrame && isProcessing)
numToRun = toRun.length;
}
return callback;
},
cancel: (callback) => {
const index = toRunNextFrame.indexOf(callback);
if (index !== -1)
toRunNextFrame.splice(index, 1);
toKeepAlive.delete(callback);
},
process: (frameData) => {
if (isProcessing) {
flushNextFrame = true;
return;
}
isProcessing = true;
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
toRunNextFrame.length = 0;
numToRun = toRun.length;
if (numToRun) {
for (let i = 0; i < numToRun; i++) {
const callback = toRun[i];
callback(frameData);
if (toKeepAlive.has(callback)) {
step.schedule(callback);
runNextFrame();
}
}
}
isProcessing = false;
if (flushNextFrame) {
flushNextFrame = false;
step.process(frameData);
}
},
};
return step;
}
export { createRenderStep };
+64
View File
@@ -0,0 +1,64 @@
import { onNextFrame, defaultTimestep } from './on-next-frame.mjs';
import { createRenderStep } from './create-render-step.mjs';
const maxElapsed = 40;
let useDefaultElapsed = true;
let runNextFrame = false;
let isProcessing = false;
const frame = {
delta: 0,
timestamp: 0,
};
const stepsOrder = [
"read",
"update",
"preRender",
"render",
"postRender",
];
const steps = stepsOrder.reduce((acc, key) => {
acc[key] = createRenderStep(() => (runNextFrame = true));
return acc;
}, {});
const sync = stepsOrder.reduce((acc, key) => {
const step = steps[key];
acc[key] = (process, keepAlive = false, immediate = false) => {
if (!runNextFrame)
startLoop();
return step.schedule(process, keepAlive, immediate);
};
return acc;
}, {});
const cancelSync = stepsOrder.reduce((acc, key) => {
acc[key] = steps[key].cancel;
return acc;
}, {});
const flushSync = stepsOrder.reduce((acc, key) => {
acc[key] = () => steps[key].process(frame);
return acc;
}, {});
const processStep = (stepId) => steps[stepId].process(frame);
const processFrame = (timestamp) => {
runNextFrame = false;
frame.delta = useDefaultElapsed
? defaultTimestep
: Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1);
frame.timestamp = timestamp;
isProcessing = true;
stepsOrder.forEach(processStep);
isProcessing = false;
if (runNextFrame) {
useDefaultElapsed = false;
onNextFrame(processFrame);
}
};
const startLoop = () => {
runNextFrame = true;
useDefaultElapsed = true;
if (!isProcessing)
onNextFrame(processFrame);
};
const getFrameData = () => frame;
export default sync;
export { cancelSync, flushSync, getFrameData };
+9
View File
@@ -0,0 +1,9 @@
const defaultTimestep = (1 / 60) * 1000;
const getCurrentTime = typeof performance !== "undefined"
? () => performance.now()
: () => Date.now();
const onNextFrame = typeof window !== "undefined"
? (callback) => window.requestAnimationFrame(callback)
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
export { defaultTimestep, onNextFrame };
+130
View File
@@ -0,0 +1,130 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const defaultTimestep = (1 / 60) * 1000;
const getCurrentTime = typeof performance !== "undefined"
? () => performance.now()
: () => Date.now();
const onNextFrame = typeof window !== "undefined"
? (callback) => window.requestAnimationFrame(callback)
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
function createRenderStep(runNextFrame) {
let toRun = [];
let toRunNextFrame = [];
let numToRun = 0;
let isProcessing = false;
let flushNextFrame = false;
const toKeepAlive = new WeakSet();
const step = {
schedule: (callback, keepAlive = false, immediate = false) => {
const addToCurrentFrame = immediate && isProcessing;
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
if (keepAlive)
toKeepAlive.add(callback);
if (buffer.indexOf(callback) === -1) {
buffer.push(callback);
if (addToCurrentFrame && isProcessing)
numToRun = toRun.length;
}
return callback;
},
cancel: (callback) => {
const index = toRunNextFrame.indexOf(callback);
if (index !== -1)
toRunNextFrame.splice(index, 1);
toKeepAlive.delete(callback);
},
process: (frameData) => {
if (isProcessing) {
flushNextFrame = true;
return;
}
isProcessing = true;
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
toRunNextFrame.length = 0;
numToRun = toRun.length;
if (numToRun) {
for (let i = 0; i < numToRun; i++) {
const callback = toRun[i];
callback(frameData);
if (toKeepAlive.has(callback)) {
step.schedule(callback);
runNextFrame();
}
}
}
isProcessing = false;
if (flushNextFrame) {
flushNextFrame = false;
step.process(frameData);
}
},
};
return step;
}
const maxElapsed = 40;
let useDefaultElapsed = true;
let runNextFrame = false;
let isProcessing = false;
const frame = {
delta: 0,
timestamp: 0,
};
const stepsOrder = [
"read",
"update",
"preRender",
"render",
"postRender",
];
const steps = stepsOrder.reduce((acc, key) => {
acc[key] = createRenderStep(() => (runNextFrame = true));
return acc;
}, {});
const sync = stepsOrder.reduce((acc, key) => {
const step = steps[key];
acc[key] = (process, keepAlive = false, immediate = false) => {
if (!runNextFrame)
startLoop();
return step.schedule(process, keepAlive, immediate);
};
return acc;
}, {});
const cancelSync = stepsOrder.reduce((acc, key) => {
acc[key] = steps[key].cancel;
return acc;
}, {});
const flushSync = stepsOrder.reduce((acc, key) => {
acc[key] = () => steps[key].process(frame);
return acc;
}, {});
const processStep = (stepId) => steps[stepId].process(frame);
const processFrame = (timestamp) => {
runNextFrame = false;
frame.delta = useDefaultElapsed
? defaultTimestep
: Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1);
frame.timestamp = timestamp;
isProcessing = true;
stepsOrder.forEach(processStep);
isProcessing = false;
if (runNextFrame) {
useDefaultElapsed = false;
onNextFrame(processFrame);
}
};
const startLoop = () => {
runNextFrame = true;
useDefaultElapsed = true;
if (!isProcessing)
onNextFrame(processFrame);
};
const getFrameData = () => frame;
exports.cancelSync = cancelSync;
exports.default = sync;
exports.flushSync = flushSync;
exports.getFrameData = getFrameData;
+136
View File
@@ -0,0 +1,136 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.framesync = {}));
}(this, (function (exports) { 'use strict';
const defaultTimestep = (1 / 60) * 1000;
const getCurrentTime = typeof performance !== "undefined"
? () => performance.now()
: () => Date.now();
const onNextFrame = typeof window !== "undefined"
? (callback) => window.requestAnimationFrame(callback)
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
function createRenderStep(runNextFrame) {
let toRun = [];
let toRunNextFrame = [];
let numToRun = 0;
let isProcessing = false;
let flushNextFrame = false;
const toKeepAlive = new WeakSet();
const step = {
schedule: (callback, keepAlive = false, immediate = false) => {
const addToCurrentFrame = immediate && isProcessing;
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
if (keepAlive)
toKeepAlive.add(callback);
if (buffer.indexOf(callback) === -1) {
buffer.push(callback);
if (addToCurrentFrame && isProcessing)
numToRun = toRun.length;
}
return callback;
},
cancel: (callback) => {
const index = toRunNextFrame.indexOf(callback);
if (index !== -1)
toRunNextFrame.splice(index, 1);
toKeepAlive.delete(callback);
},
process: (frameData) => {
if (isProcessing) {
flushNextFrame = true;
return;
}
isProcessing = true;
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
toRunNextFrame.length = 0;
numToRun = toRun.length;
if (numToRun) {
for (let i = 0; i < numToRun; i++) {
const callback = toRun[i];
callback(frameData);
if (toKeepAlive.has(callback)) {
step.schedule(callback);
runNextFrame();
}
}
}
isProcessing = false;
if (flushNextFrame) {
flushNextFrame = false;
step.process(frameData);
}
},
};
return step;
}
const maxElapsed = 40;
let useDefaultElapsed = true;
let runNextFrame = false;
let isProcessing = false;
const frame = {
delta: 0,
timestamp: 0,
};
const stepsOrder = [
"read",
"update",
"preRender",
"render",
"postRender",
];
const steps = stepsOrder.reduce((acc, key) => {
acc[key] = createRenderStep(() => (runNextFrame = true));
return acc;
}, {});
const sync = stepsOrder.reduce((acc, key) => {
const step = steps[key];
acc[key] = (process, keepAlive = false, immediate = false) => {
if (!runNextFrame)
startLoop();
return step.schedule(process, keepAlive, immediate);
};
return acc;
}, {});
const cancelSync = stepsOrder.reduce((acc, key) => {
acc[key] = steps[key].cancel;
return acc;
}, {});
const flushSync = stepsOrder.reduce((acc, key) => {
acc[key] = () => steps[key].process(frame);
return acc;
}, {});
const processStep = (stepId) => steps[stepId].process(frame);
const processFrame = (timestamp) => {
runNextFrame = false;
frame.delta = useDefaultElapsed
? defaultTimestep
: Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1);
frame.timestamp = timestamp;
isProcessing = true;
stepsOrder.forEach(processStep);
isProcessing = false;
if (runNextFrame) {
useDefaultElapsed = false;
onNextFrame(processFrame);
}
};
const startLoop = () => {
runNextFrame = true;
useDefaultElapsed = true;
if (!isProcessing)
onNextFrame(processFrame);
};
const getFrameData = () => frame;
exports.cancelSync = cancelSync;
exports.default = sync;
exports.flushSync = flushSync;
exports.getFrameData = getFrameData;
Object.defineProperty(exports, '__esModule', { value: true });
})));
+1
View File
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).framesync={})}(this,(function(e){"use strict";const t=1/60*1e3,n="undefined"!=typeof performance?()=>performance.now():()=>Date.now(),o="undefined"!=typeof window?e=>window.requestAnimationFrame(e):e=>setTimeout((()=>e(n())),t);let s=!0,c=!1,d=!1;const r={delta:0,timestamp:0},a=["read","update","preRender","render","postRender"],f=a.reduce(((e,t)=>(e[t]=function(e){let t=[],n=[],o=0,s=!1,c=!1;const d=new WeakSet,r={schedule:(e,c=!1,r=!1)=>{const a=r&&s,f=a?t:n;return c&&d.add(e),-1===f.indexOf(e)&&(f.push(e),a&&s&&(o=t.length)),e},cancel:e=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1),d.delete(e)},process:a=>{if(s)c=!0;else{if(s=!0,[t,n]=[n,t],n.length=0,o=t.length,o)for(let n=0;n<o;n++){const o=t[n];o(a),d.has(o)&&(r.schedule(o),e())}s=!1,c&&(c=!1,r.process(a))}}};return r}((()=>c=!0)),e)),{}),i=a.reduce(((e,t)=>{const n=f[t];return e[t]=(e,t=!1,o=!1)=>(c||h(),n.schedule(e,t,o)),e}),{}),u=a.reduce(((e,t)=>(e[t]=f[t].cancel,e)),{}),l=a.reduce(((e,t)=>(e[t]=()=>f[t].process(r),e)),{}),p=e=>f[e].process(r),m=e=>{c=!1,r.delta=s?t:Math.max(Math.min(e-r.timestamp,40),1),r.timestamp=e,d=!0,a.forEach(p),d=!1,c&&(s=!1,o(m))},h=()=>{c=!0,s=!0,d||o(m)};e.cancelSync=u,e.default=i,e.flushSync=l,e.getFrameData=()=>r,Object.defineProperty(e,"__esModule",{value:!0})}));
+1
View File
@@ -0,0 +1 @@
export {};
+126
View File
@@ -0,0 +1,126 @@
import { __awaiter } from "tslib";
import sync, { cancelSync, flushSync } from "../";
import { onNextFrame } from "../on-next-frame";
describe("onNextFrame", () => {
it("fires callback on following frame", () => {
return new Promise((resolve) => onNextFrame(resolve));
});
});
describe("sync", () => {
it("fires callbacks in the correct order", () => {
return new Promise((resolve, reject) => {
const order = [];
sync.read(() => order.push(0));
sync.update(() => order.push(1));
sync.preRender(() => order.push(2));
sync.render(() => order.push(3));
sync.postRender(() => {
order.push(4);
if (order[0] === 0 &&
order[1] === 1 &&
order[2] === 2 &&
order[3] === 3 &&
order[4] === 4) {
resolve();
}
else {
reject(order);
}
});
});
});
it("cancels callbacks", () => {
return new Promise((resolve, reject) => {
let hasFired = false;
const process = sync.render(() => (hasFired = true));
sync.update(() => cancelSync.render(process));
sync.postRender(() => (hasFired ? reject(hasFired) : resolve()));
});
});
it("fires callback on current frame if scheduled with `true` within the same step", () => {
return new Promise((resolve, reject) => {
let v = 0;
sync.update(({ timestamp: prevTimestamp }) => {
v++;
sync.update(({ timestamp }) => {
v++;
if (timestamp !== prevTimestamp) {
reject(`${timestamp} ${prevTimestamp}`);
}
}, false, true);
});
sync.render(() => (v === 2 ? resolve() : reject(v)));
});
});
it("fires callback on next frame if scheduled with `true` outside the same step", () => {
return new Promise((resolve, reject) => {
let v = 0;
sync.update(() => v++);
sync.update(() => v++, false, true);
sync.render(() => (v === 2 ? resolve() : reject()));
});
});
it("uses default elapsed time if first fire", () => {
return new Promise((resolve, reject) => {
sync.update(({ delta: defaultElapsed }) => {
setTimeout(() => sync.update(({ delta }) => delta === defaultElapsed
? resolve()
: reject(defaultElapsed, delta)), 50);
});
});
});
it("correctly cancels", () => {
return new Promise((resolve, reject) => {
const callback = () => reject();
sync.read(() => cancelSync.update(callback));
sync.update(callback);
sync.render(() => resolve());
});
});
it("correctly keeps alive", () => {
return new Promise((resolve) => {
let v = 0;
sync.update(() => v++, true);
sync.render(() => v === 2 && resolve(), true);
});
});
it("correctly cancels a keepAlive process", () => {
return new Promise((resolve, reject) => {
let updateCount = 0;
let renderCount = 0;
const update = sync.update(() => {
updateCount++;
if (updateCount === 4)
cancelSync.update(update);
}, true);
sync.render(() => {
renderCount++;
if (renderCount === 6) {
if (renderCount !== updateCount) {
resolve();
}
else {
reject([renderCount, updateCount]);
}
}
}, true);
});
});
it("correctly keeps alive after a flush", () => __awaiter(void 0, void 0, void 0, function* () {
const promise = new Promise((resolve) => {
let v = 0;
sync.update(() => {
if (v === 2)
flushSync.update();
}, true);
sync.update(() => {
v++;
if (v > 6)
resolve(true);
}, true);
});
flushSync.update();
return expect(promise).resolves.toBe(true);
}));
});
//# sourceMappingURL=test.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import { Step } from "./types";
export declare function createRenderStep(runNextFrame: () => void): Step;
+55
View File
@@ -0,0 +1,55 @@
export function createRenderStep(runNextFrame) {
let toRun = [];
let toRunNextFrame = [];
let numToRun = 0;
let isProcessing = false;
let flushNextFrame = false;
const toKeepAlive = new WeakSet();
const step = {
schedule: (callback, keepAlive = false, immediate = false) => {
const addToCurrentFrame = immediate && isProcessing;
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
if (keepAlive)
toKeepAlive.add(callback);
if (buffer.indexOf(callback) === -1) {
buffer.push(callback);
if (addToCurrentFrame && isProcessing)
numToRun = toRun.length;
}
return callback;
},
cancel: (callback) => {
const index = toRunNextFrame.indexOf(callback);
if (index !== -1)
toRunNextFrame.splice(index, 1);
toKeepAlive.delete(callback);
},
process: (frameData) => {
if (isProcessing) {
flushNextFrame = true;
return;
}
isProcessing = true;
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
toRunNextFrame.length = 0;
numToRun = toRun.length;
if (numToRun) {
for (let i = 0; i < numToRun; i++) {
const callback = toRun[i];
callback(frameData);
if (toKeepAlive.has(callback)) {
step.schedule(callback);
runNextFrame();
}
}
}
isProcessing = false;
if (flushNextFrame) {
flushNextFrame = false;
step.process(frameData);
}
},
};
return step;
}
//# sourceMappingURL=create-render-step.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"create-render-step.js","sourceRoot":"","sources":["../src/create-render-step.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,YAAwB;IAKrD,IAAI,KAAK,GAAc,EAAE,CAAA;IACzB,IAAI,cAAc,GAAc,EAAE,CAAA;IAKlC,IAAI,QAAQ,GAAG,CAAC,CAAA;IAMhB,IAAI,YAAY,GAAG,KAAK,CAAA;IAExB,IAAI,cAAc,GAAG,KAAK,CAAA;IAK1B,MAAM,WAAW,GAAG,IAAI,OAAO,EAAW,CAAA;IAE1C,MAAM,IAAI,GAAS;QAIf,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,EAAE;YACzD,MAAM,iBAAiB,GAAG,SAAS,IAAI,YAAY,CAAA;YACnD,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAA;YAEzD,IAAI,SAAS;gBAAE,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YAGxC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE;gBACjC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBAGrB,IAAI,iBAAiB,IAAI,YAAY;oBAAE,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAA;aACjE;YAED,OAAO,QAAQ,CAAA;QACnB,CAAC;QAKD,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE;YACjB,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YAC9C,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;YAEjD,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAChC,CAAC;QAKD,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE;YAMnB,IAAI,YAAY,EAAE;gBACd,cAAc,GAAG,IAAI,CAAA;gBACrB,OAAM;aACT;YAED,YAAY,GAAG,IAAI,CAGlB;YAAA,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;YAGlD,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA;YAGzB,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAA;YAEvB,IAAI,QAAQ,EAAE;gBACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE;oBAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;oBAEzB,QAAQ,CAAC,SAAS,CAAC,CAAA;oBAEnB,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;wBAC3B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;wBACvB,YAAY,EAAE,CAAA;qBACjB;iBACJ;aACJ;YAED,YAAY,GAAG,KAAK,CAAA;YAEpB,IAAI,cAAc,EAAE;gBAChB,cAAc,GAAG,KAAK,CAAA;gBACtB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;aAC1B;QACL,CAAC;KACJ,CAAA;IAED,OAAO,IAAI,CAAA;AACf,CAAC"}
+10
View File
@@ -0,0 +1,10 @@
import { Process, FrameData, CancelSync, FlushSync, Sync } from "./types";
declare const sync: Sync;
declare const cancelSync: CancelSync;
declare const flushSync: FlushSync;
declare const getFrameData: () => {
delta: number;
timestamp: number;
};
export default sync;
export { cancelSync, flushSync, getFrameData, FrameData, Process };
+63
View File
@@ -0,0 +1,63 @@
import { onNextFrame, defaultTimestep } from "./on-next-frame";
import { createRenderStep } from "./create-render-step";
const maxElapsed = 40;
let useDefaultElapsed = true;
let runNextFrame = false;
let isProcessing = false;
const frame = {
delta: 0,
timestamp: 0,
};
const stepsOrder = [
"read",
"update",
"preRender",
"render",
"postRender",
];
const steps = stepsOrder.reduce((acc, key) => {
acc[key] = createRenderStep(() => (runNextFrame = true));
return acc;
}, {});
const sync = stepsOrder.reduce((acc, key) => {
const step = steps[key];
acc[key] = (process, keepAlive = false, immediate = false) => {
if (!runNextFrame)
startLoop();
return step.schedule(process, keepAlive, immediate);
};
return acc;
}, {});
const cancelSync = stepsOrder.reduce((acc, key) => {
acc[key] = steps[key].cancel;
return acc;
}, {});
const flushSync = stepsOrder.reduce((acc, key) => {
acc[key] = () => steps[key].process(frame);
return acc;
}, {});
const processStep = (stepId) => steps[stepId].process(frame);
const processFrame = (timestamp) => {
runNextFrame = false;
frame.delta = useDefaultElapsed
? defaultTimestep
: Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1);
frame.timestamp = timestamp;
isProcessing = true;
stepsOrder.forEach(processStep);
isProcessing = false;
if (runNextFrame) {
useDefaultElapsed = false;
onNextFrame(processFrame);
}
};
const startLoop = () => {
runNextFrame = true;
useDefaultElapsed = true;
if (!isProcessing)
onNextFrame(processFrame);
};
const getFrameData = () => frame;
export default sync;
export { cancelSync, flushSync, getFrameData };
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="node" />
export declare const defaultTimestep: number;
export declare const onNextFrame: ((callback: FrameRequestCallback) => number) | ((callback: FrameRequestCallback) => NodeJS.Timeout);
+8
View File
@@ -0,0 +1,8 @@
export const defaultTimestep = (1 / 60) * 1000;
const getCurrentTime = typeof performance !== "undefined"
? () => performance.now()
: () => Date.now();
export const onNextFrame = typeof window !== "undefined"
? (callback) => window.requestAnimationFrame(callback)
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
//# sourceMappingURL=on-next-frame.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"on-next-frame.js","sourceRoot":"","sources":["../src/on-next-frame.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAA;AAE9C,MAAM,cAAc,GAChB,OAAO,WAAW,KAAK,WAAW;IAC9B,CAAC,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;IACzB,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;AAE1B,MAAM,CAAC,MAAM,WAAW,GACpB,OAAO,MAAM,KAAK,WAAW;IACzB,CAAC,CAAC,CAAC,QAA8B,EAAE,EAAE,CAC/B,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC;IAC5C,CAAC,CAAC,CAAC,QAA8B,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,eAAe,CAAC,CAAA"}
+24
View File
@@ -0,0 +1,24 @@
export declare type FrameData = {
delta: number;
timestamp: number;
};
export declare type Process = (data: FrameData) => void;
export declare type Schedule = (process: Process, keepAlive?: boolean, immediate?: boolean) => Process;
export interface Step {
schedule: Schedule;
cancel: (process: Process) => void;
process: (frame: FrameData) => void;
}
export declare type StepId = "read" | "update" | "postUpdate" | "preRender" | "render" | "postRender";
export declare type Sync = {
[key in StepId]: Schedule;
};
export declare type Steps = {
[key in StepId]: Step;
};
export declare type CancelSync = {
[key in StepId]: (process: Process) => void;
};
export declare type FlushSync = {
[key in StepId]: () => void;
};
+1
View File
@@ -0,0 +1 @@
//# sourceMappingURL=types.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
+65
View File
@@ -0,0 +1,65 @@
{
"name": "framesync",
"license": "MIT",
"version": "6.1.2",
"description": "A frame-synced render loop for JavaScript",
"author": "Matt Perry",
"homepage": "https://popmotion.io",
"main": "dist/framesync.cjs.js",
"types": "lib/index.d.ts",
"module": "dist/es/index.mjs",
"jsnext:main": "dist/es/index.mjs",
"unpkg": "./dist/framesync.min.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./dist/es/index.mjs",
"require": "./dist/framesync.cjs.js",
"default": "./dist/framesync.cjs.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "tsc -p . && rollup -c && yarn measure",
"watch": "rollup -c -w",
"lint": "tslint -c tslint.json 'src/**/*.{ts}'",
"test": "jest",
"measure": "gzip -c dist/framesync.min.js | wc -c",
"prepublishOnly": "npm run test && npm run build"
},
"files": [
"lib",
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/Popmotion/popmotion.git"
},
"keywords": [
"animation",
"raf"
],
"bugs": {
"url": "https://github.com/Popmotion/popmotion/issues"
},
"jest": {
"moduleFileExtensions": [
"ts",
"js"
],
"transform": {
"\\.(ts)$": "../../../node_modules/ts-jest/preprocessor.js"
},
"testRegex": "/_tests/.*\\.(ts|js)$",
"rootDir": "src"
},
"prettier": {
"printWidth": 80,
"tabWidth": 4,
"semi": false,
"trailingComma": "es5"
},
"dependencies": {
"tslib": "2.4.0"
}
}