-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Component } from '@angular/core'; | ||
import { injectFps } from 'ngxtension/inject-fps'; | ||
|
||
@Component({ | ||
standalone: true, | ||
host: { | ||
style: 'display: block; margin: 12px', | ||
}, | ||
template: ` | ||
<div>FPS: {{ fps() }}</div> | ||
<button>btn1</button> | ||
<button>btn2</button> | ||
<button>btn3</button> | ||
`, | ||
}) | ||
export default class InjectFpsCmp { | ||
fps = injectFps(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/inject-fps | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/inject-fps`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "ngxtension/inject-fps", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/inject-fps/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["inject-fps"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/eslint:lint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": [ | ||
"libs/ngxtension/inject-fps/**/*.ts", | ||
"libs/ngxtension/inject-fps/**/*.html" | ||
] | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './inject-fps'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { | ||
runInInjectionContext, | ||
signal, | ||
type Injector, | ||
type Signal, | ||
} from '@angular/core'; | ||
import { assertInjector } from 'ngxtension/assert-injector'; | ||
import { injectRafFn } from 'ngxtension/inject-raf-fn'; | ||
|
||
export interface InjectFpsOptions { | ||
/** | ||
* Calculate the FPS on every x frames. | ||
* @default 10 | ||
*/ | ||
every?: number; | ||
|
||
/** | ||
* The injector to use. | ||
*/ | ||
injector?: Injector; | ||
} | ||
|
||
export function injectFps(options?: InjectFpsOptions): Signal<number> { | ||
const injector = assertInjector(injectFps, options?.injector); | ||
|
||
return runInInjectionContext(injector, () => { | ||
const fps = signal(0); | ||
if (typeof performance === 'undefined') return fps.asReadonly(); | ||
|
||
const every = options?.every ?? 10; | ||
|
||
let last = performance.now(); | ||
let ticks = 0; | ||
|
||
injectRafFn( | ||
() => { | ||
ticks += 1; | ||
if (ticks >= every) { | ||
const now = performance.now(); | ||
const diff = now - last; | ||
fps.set(Math.round(1000 / (diff / ticks))); | ||
last = now; | ||
ticks = 0; | ||
} | ||
}, | ||
{ injector } | ||
); | ||
|
||
return fps; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/inject-raf-fn | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/inject-raf-fn`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "ngxtension/inject-raf-fn", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/inject-raf-fn/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["inject-raf-fn"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/eslint:lint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": [ | ||
"libs/ngxtension/inject-raf-fn/**/*.ts", | ||
"libs/ngxtension/inject-raf-fn/**/*.html" | ||
] | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './inject-raf-fn'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { isPlatformServer } from '@angular/common'; | ||
import { | ||
NgZone, | ||
PLATFORM_ID, | ||
inject, | ||
runInInjectionContext, | ||
signal, | ||
type Injector, | ||
type Signal, | ||
} from '@angular/core'; | ||
import { assertInjector } from 'ngxtension/assert-injector'; | ||
|
||
export interface InjectRafFnCallbackArguments { | ||
/** | ||
* Time elapsed between this and the last frame. | ||
*/ | ||
delta: number; | ||
|
||
/** | ||
* Time elapsed since the creation of the web page. See {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin Time origin}. | ||
*/ | ||
timestamp: DOMHighResTimeStamp; | ||
} | ||
|
||
export interface UseRafFnOptions { | ||
/** | ||
* Start the requestAnimationFrame loop immediately on creation | ||
* | ||
* @default true | ||
*/ | ||
immediate?: boolean; | ||
/** | ||
* The maximum frame per second to execute the function. | ||
* Set to `undefined` to disable the limit. | ||
* | ||
* @default undefined | ||
*/ | ||
fpsLimit?: number; | ||
|
||
/** | ||
* The injector to use. | ||
*/ | ||
injector?: Injector; | ||
} | ||
|
||
export interface RafFn { | ||
/** | ||
* A signal to indicate whether an instance is active | ||
*/ | ||
isActive: Signal<boolean>; | ||
|
||
/** | ||
* Temporary pause the effect from executing | ||
*/ | ||
pause: () => void; | ||
|
||
/** | ||
* Resume the effects | ||
*/ | ||
resume: () => void; | ||
} | ||
|
||
/** | ||
* Call function on every `requestAnimationFrame`. With controls of pausing and resuming. | ||
* | ||
* @param fn | ||
* @param options | ||
*/ | ||
export function injectRafFn( | ||
fn: (args: InjectRafFnCallbackArguments) => void, | ||
options: UseRafFnOptions = {} | ||
): RafFn { | ||
const injector = assertInjector(injectRafFn, options?.injector); | ||
|
||
return runInInjectionContext(injector, () => { | ||
const ngZone = inject(NgZone); | ||
const isServer = isPlatformServer(inject(PLATFORM_ID)); | ||
|
||
if (isServer) { | ||
return { | ||
isActive: signal(false).asReadonly(), | ||
pause: () => {}, | ||
resume: () => {}, | ||
}; | ||
} | ||
|
||
const { immediate = true, fpsLimit = undefined } = options; | ||
|
||
const isActive = signal(false); | ||
|
||
const intervalLimit = fpsLimit ? 1000 / fpsLimit : null; | ||
let previousFrameTimestamp = 0; | ||
let rafId: null | number = null; | ||
|
||
const loop = (timestamp: DOMHighResTimeStamp) => { | ||
if (!isActive() || !window) return; | ||
|
||
const delta = timestamp - (previousFrameTimestamp || timestamp); | ||
|
||
if (intervalLimit && delta < intervalLimit) { | ||
rafId = ngZone.runOutsideAngular(() => | ||
window.requestAnimationFrame(loop) | ||
); | ||
return; | ||
} | ||
|
||
fn({ delta, timestamp }); | ||
|
||
previousFrameTimestamp = timestamp; | ||
|
||
rafId = ngZone.runOutsideAngular(() => | ||
window.requestAnimationFrame(loop) | ||
); | ||
}; | ||
|
||
const resume = () => { | ||
if (!isActive() && window) { | ||
isActive.set(true); | ||
rafId = ngZone.runOutsideAngular(() => | ||
window.requestAnimationFrame(loop) | ||
); | ||
} | ||
}; | ||
|
||
const pause = () => { | ||
isActive.set(false); | ||
if (rafId && typeof rafId === 'number' && window) { | ||
ngZone.runOutsideAngular(() => window.cancelAnimationFrame(rafId!)); | ||
rafId = null; | ||
} | ||
}; | ||
|
||
if (immediate) resume(); | ||
|
||
return { | ||
isActive: isActive.asReadonly(), | ||
pause, | ||
resume, | ||
}; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters