UNPKG

4.15 kBJavaScriptView Raw
1const MILLISECONDS_IN_SECOND = 1000;
2
3const FILTER_STRENGTH = Symbol();
4const SAMPLE_RATE = Symbol();
5const HISTORY_DURATION = Symbol();
6const FPS = Symbol();
7const HISTORY = Symbol();
8const SAMPLE_INTERVAL = Symbol();
9const ON_SAMPLE = Symbol();
10
11/**
12 * ### Usage
13 * ```
14 * import FrameRate from 'framerate-history';
15 * ```
16 *
17 * @module FrameRate
18 *
19 * @param {Object} settings
20 * @param {Function} [settings.onSample]
21 * @param {Number} [settings.filterStrength=5]
22 * @param {Number} [settings.historyDuration=30]
23 * @param {Number} [settings.sampleRate=10]
24 */
25export default class FrameRate {
26 constructor(settings) {
27 const self = this;
28 let frameTime = 0;
29 let lastLoop = performance.now();
30
31 self[HISTORY] = [];
32
33 self.onSample(settings.onSample);
34 self.filterStrength(settings.filterStrength || 5);
35 self.historyDuration(settings.historyDuration || 30);
36 self.sampleRate(settings.sampleRate || 10);
37
38 const refreshLoop = () => {
39 window.requestAnimationFrame((now) => {
40 frameTime += (now - lastLoop - frameTime) / self[FILTER_STRENGTH];
41 lastLoop = now;
42 self[FPS] = Math.round(MILLISECONDS_IN_SECOND / frameTime);
43 refreshLoop();
44 });
45 };
46
47 refreshLoop();
48 }
49
50 /**
51 * @type {number}
52 */
53 get fps() {
54 return this[FPS];
55 }
56
57 /**
58 * @type {array}
59 */
60 get history() {
61 return this[HISTORY];
62 }
63
64 /**
65 * Sets a filter on the frame rate calculation. Setting to 1 will effectively turn off the filter, the higher the
66 * number the more smooth the curve over time. See this stackoverflow question for details:
67 * https://stackoverflow.com/questions/4787431/check-fps-in-js
68 *
69 * @method filterStrength
70 * @instance
71 *
72 * @param {Number} [value]
73 *
74 * @returns {frameRate|Number}
75 */
76 filterStrength(value) {
77 const self = this;
78
79 if (value !== undefined) {
80 self[FILTER_STRENGTH] = value;
81
82 return self;
83 }
84
85 return self[FILTER_STRENGTH];
86 }
87
88 /**
89 * The rate to take samples. Setting to 0 will clear the interval. If the interval is prevented from executing at the desired rate, the history will get filled in with the current frame rate in an attempt to keep the history as accurate as possible.
90 *
91 * @method sampleRate
92 * @instance
93 *
94 * @param {Number} [value] - Samples per second
95 *
96 * @returns {frameRate|Number}
97 */
98 sampleRate(value) {
99 const self = this;
100 let lastSample;
101 let now;
102
103 if (value !== undefined) {
104 const DURATION = MILLISECONDS_IN_SECOND / value;
105 self[SAMPLE_RATE] = value;
106 self.historyDuration(self.historyDuration());
107 lastSample = performance.now();
108
109 clearInterval(self[SAMPLE_INTERVAL]);
110
111 if (self[SAMPLE_RATE] && self.historyDuration()) {
112 self[SAMPLE_INTERVAL] = setInterval(() => {
113 now = performance.now();
114
115 while (lastSample + DURATION < now) {
116 self[HISTORY].push(self[FPS]);
117 self[HISTORY].shift();
118 lastSample += DURATION;
119 }
120
121 if (self[ON_SAMPLE]) {
122 self[ON_SAMPLE](self[HISTORY]);
123 }
124 }, DURATION);
125 }
126
127 return self;
128 }
129
130 return self[SAMPLE_RATE];
131 }
132
133 /**
134 * The callback will get called for every sample taken.
135 *
136 * @method onSample
137 * @instance
138 *
139 * @param {Function} [callback]
140 *
141 * @return {frameRate|Function}
142 */
143 onSample(callback) {
144 const self = this;
145
146 if (callback !== undefined) {
147 self[ON_SAMPLE] = callback;
148
149 return self;
150 }
151
152 return self[ON_SAMPLE];
153 }
154
155 /**
156 * Defines the duration of tracked history in seconds.
157 *
158 * @method historyDuration
159 * @instance
160 *
161 * @param {Number} [value] - Seconds
162 *
163 * @returns {frameRate|Number}
164 */
165 historyDuration(value) {
166 const self = this;
167
168 if (value !== undefined) {
169 self[HISTORY_DURATION] = value;
170 const totalHistory = self[SAMPLE_RATE] * self[HISTORY_DURATION];
171
172 while (self[HISTORY].length < totalHistory) {
173 self[HISTORY].unshift(0);
174 }
175 while (self[HISTORY].length > totalHistory) {
176 self[HISTORY].shift();
177 }
178
179 return self;
180 }
181
182 return self[HISTORY_DURATION];
183 }
184}