vite-uni-dev-tool 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +46 -0
  2. package/dist/const.cjs +1 -1
  3. package/dist/const.d.ts +12 -0
  4. package/dist/const.d.ts.map +1 -1
  5. package/dist/const.js +1 -1
  6. package/dist/core-shared.d.ts +1 -1
  7. package/dist/core-shared.d.ts.map +1 -1
  8. package/dist/core-shared.js +1 -1
  9. package/dist/core.d.ts +10 -3
  10. package/dist/core.d.ts.map +1 -1
  11. package/dist/core.js +2 -2
  12. package/dist/i18n/locales/en.cjs +1 -1
  13. package/dist/i18n/locales/en.d.ts +81 -0
  14. package/dist/i18n/locales/en.d.ts.map +1 -1
  15. package/dist/i18n/locales/en.js +1 -1
  16. package/dist/i18n/locales/zh-Hans.cjs +1 -1
  17. package/dist/i18n/locales/zh-Hans.d.ts +82 -1
  18. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  19. package/dist/i18n/locales/zh-Hans.js +1 -1
  20. package/dist/modules/devConsole/index.cjs +1 -1
  21. package/dist/modules/devConsole/index.js +3 -3
  22. package/dist/modules/devEvent/index.cjs +3 -3
  23. package/dist/modules/devEvent/index.d.ts +1 -0
  24. package/dist/modules/devEvent/index.d.ts.map +1 -1
  25. package/dist/modules/devEvent/index.js +3 -3
  26. package/dist/modules/devIntercept/index.cjs +14 -13
  27. package/dist/modules/devIntercept/index.d.ts +19 -0
  28. package/dist/modules/devIntercept/index.d.ts.map +1 -1
  29. package/dist/modules/devIntercept/index.js +14 -13
  30. package/dist/modules/devStore/index.cjs +1 -1
  31. package/dist/modules/devStore/index.d.ts +21 -0
  32. package/dist/modules/devStore/index.d.ts.map +1 -1
  33. package/dist/modules/devStore/index.js +1 -1
  34. package/dist/plugins/uniDevTool/transform/transformMain.cjs +1 -1
  35. package/dist/plugins/uniDevTool/transform/transformMain.js +1 -1
  36. package/dist/type.d.ts +47 -2
  37. package/dist/type.d.ts.map +1 -1
  38. package/dist/utils/language.cjs +1 -1
  39. package/dist/utils/language.d.ts.map +1 -1
  40. package/dist/utils/language.js +1 -1
  41. package/dist/utils/object.cjs +1 -1
  42. package/dist/utils/object.d.ts.map +1 -1
  43. package/dist/utils/object.js +1 -1
  44. package/dist/v3/DevTool/components/BluetoothList/BluetoothItem.vue +199 -0
  45. package/dist/v3/DevTool/components/BluetoothList/BluetoothTool.vue +730 -0
  46. package/dist/v3/DevTool/components/BluetoothList/index.vue +167 -0
  47. package/dist/v3/{CaptureScreen → DevTool/components/CaptureScreen}/index.vue +109 -109
  48. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/ConsoleItem.vue +225 -224
  49. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/RunJSInput.vue +247 -249
  50. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/index.vue +171 -160
  51. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/staticTips.ts +1145 -1145
  52. package/dist/v3/{DevToolButton → DevTool/components/DevToolButton}/index.vue +7 -4
  53. package/dist/v3/{DevToolTitle → DevTool/components/DevToolTitle}/index.vue +24 -24
  54. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/DevToolOverlay.vue +197 -182
  55. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/const.ts +28 -5
  56. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/dataUtils.ts +48 -48
  57. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolData.ts +387 -338
  58. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolHandlers.ts +629 -549
  59. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolOverlay.ts +197 -184
  60. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/index.vue +67 -16
  61. package/dist/v3/{ElEvent → DevTool/components/ElEvent}/ElEventItem.vue +105 -105
  62. package/dist/v3/{ElEvent → DevTool/components/ElEvent}/index.vue +106 -109
  63. package/dist/v3/{Instance → DevTool/components/Instance}/components/InstanceTreeNode.vue +265 -265
  64. package/dist/v3/{Instance → DevTool/components/Instance}/flatten.ts +226 -226
  65. package/dist/v3/{Instance → DevTool/components/Instance}/index.vue +94 -94
  66. package/dist/v3/{Instance → DevTool/components/Instance}/registry.ts +49 -49
  67. package/dist/v3/{Instance → DevTool/components/Instance}/transformTree.ts +375 -375
  68. package/dist/v3/{Instance → DevTool/components/Instance}/transformTreeCtx.ts +268 -268
  69. package/dist/v3/{Instance → DevTool/components/Instance}/typing.d.ts +43 -43
  70. package/dist/v3/{InstanceDetail → DevTool/components/InstanceDetail}/index.vue +485 -485
  71. package/dist/v3/{JsonDetail → DevTool/components/JsonDetail}/index.vue +70 -70
  72. package/dist/v3/{NFCList → DevTool/components/NFCList}/NFCItem.vue +112 -113
  73. package/dist/v3/{NFCList → DevTool/components/NFCList}/NFCTool.vue +454 -478
  74. package/dist/v3/{NFCList → DevTool/components/NFCList}/const.ts +56 -56
  75. package/dist/v3/{NFCList → DevTool/components/NFCList}/index.vue +94 -98
  76. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/InterceptConfig.vue +624 -608
  77. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/InterceptItem.vue +140 -140
  78. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkDetail.vue +287 -296
  79. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkIntercept.vue +88 -93
  80. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkItem.vue +163 -167
  81. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkSend.vue +589 -556
  82. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/const.ts +4 -4
  83. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/hooks/useNetworkForm.ts +86 -86
  84. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/index.vue +160 -160
  85. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/utils.ts +101 -101
  86. package/dist/v3/{Performance → DevTool/components/Performance}/index.vue +498 -495
  87. package/dist/v3/{Performance → DevTool/components/Performance}/modules/PerformanceMetrics.vue +153 -153
  88. package/dist/v3/{Performance → DevTool/components/Performance}/modules/PerformanceWidget.vue +12 -9
  89. package/dist/v3/{Performance → DevTool/components/Performance}/modules/usePerformanceChart.ts +460 -460
  90. package/dist/v3/{Performance → DevTool/components/Performance}/modules/usePerformanceData.ts +258 -258
  91. package/dist/v3/{PiniaList → DevTool/components/PiniaList}/index.vue +93 -94
  92. package/dist/v3/{RouteList → DevTool/components/RouteList}/index.vue +21 -24
  93. package/dist/v3/{RunJS → DevTool/components/RunJS}/index.vue +148 -148
  94. package/dist/v3/{ScanCodeList → DevTool/components/ScanCodeList}/ScanCodeItem.vue +97 -98
  95. package/dist/v3/{ScanCodeList → DevTool/components/ScanCodeList}/index.vue +100 -104
  96. package/dist/v3/{SettingButton → DevTool/components/SettingButton}/index.vue +45 -45
  97. package/dist/v3/{SettingList → DevTool/components/SettingList}/index.vue +218 -150
  98. package/dist/v3/DevTool/components/SettingList/modules/SettingBarrage.vue +304 -0
  99. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingDevTool.vue +212 -208
  100. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingInfo.vue +157 -119
  101. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingLanguage.vue +74 -74
  102. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingLog.vue +230 -230
  103. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingNetwork.vue +3 -3
  104. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingTheme.vue +37 -7
  105. package/dist/v3/{SettingList → DevTool/components/SettingList}/typing.d.ts +2 -2
  106. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/Line.vue +127 -116
  107. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/index.vue +8 -8
  108. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/parseCode.ts +609 -701
  109. package/dist/v3/{StorageList → DevTool/components/StorageList}/index.vue +174 -174
  110. package/dist/v3/{TransferList → DevTool/components/TransferList}/TransferDetail.vue +268 -268
  111. package/dist/v3/{TransferList → DevTool/components/TransferList}/TransferItem.vue +4 -4
  112. package/dist/v3/{TransferList → DevTool/components/TransferList}/index.vue +8 -8
  113. package/dist/v3/{UniEvent → DevTool/components/UniEvent}/UniEventItem.vue +6 -7
  114. package/dist/v3/{UniEvent → DevTool/components/UniEvent}/index.vue +6 -6
  115. package/dist/v3/{VuexList → DevTool/components/VuexList}/index.vue +84 -84
  116. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/WebSocketDetail.vue +8 -8
  117. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/WebSocketItem.vue +4 -4
  118. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/index.vue +8 -8
  119. package/dist/v3/DevTool/index.vue +179 -5
  120. package/dist/v3/{AppTransition → components/AppTransition}/index.vue +176 -170
  121. package/dist/v3/{AutoSizer → components/AutoSizer}/index.vue +192 -192
  122. package/dist/v3/{AutoSizer → components/AutoSizer}/index1.vue +184 -184
  123. package/dist/v3/{AutoSizer → components/AutoSizer}/utils.ts +49 -49
  124. package/dist/v3/components/Barrage/BarrageItem.vue +137 -0
  125. package/dist/v3/components/Barrage/index.vue +202 -0
  126. package/dist/v3/{CircularButton → components/CircularButton}/index.vue +84 -84
  127. package/dist/v3/{CustomSwiper → components/CustomSwiper}/CustomSwiperItem.vue +49 -49
  128. package/dist/v3/{CustomSwiper → components/CustomSwiper}/index.vue +104 -104
  129. package/dist/v3/{DraggableContainer → components/DraggableContainer}/index.vue +1 -1
  130. package/dist/v3/{Empty → components/Empty}/index.vue +29 -29
  131. package/dist/v3/{FilterInput → components/FilterInput}/index.vue +1 -1
  132. package/dist/v3/{FilterSelect → components/FilterSelect}/index.vue +179 -179
  133. package/dist/v3/{JsonPretty → components/JsonPretty}/components/Brackets/index.vue +27 -27
  134. package/dist/v3/{JsonPretty → components/JsonPretty}/components/Carets/index.vue +59 -59
  135. package/dist/v3/{JsonPretty → components/JsonPretty}/components/CheckController/index.vue +136 -136
  136. package/dist/v3/{JsonPretty → components/JsonPretty}/components/TreeNode/index.vue +387 -381
  137. package/dist/v3/{JsonPretty → components/JsonPretty}/hooks/useClipboard.ts +21 -21
  138. package/dist/v3/{JsonPretty → components/JsonPretty}/hooks/useError.ts +21 -21
  139. package/dist/v3/{JsonPretty → components/JsonPretty}/index.vue +16 -13
  140. package/dist/v3/{JsonPretty → components/JsonPretty}/type.ts +127 -126
  141. package/dist/v3/{JsonPretty → components/JsonPretty}/utils/index.ts +169 -169
  142. package/dist/v3/{MovableContainer → components/MovableContainer}/index.vue +1 -1
  143. package/dist/v3/{Pick → components/Pick}/index.vue +322 -322
  144. package/dist/v3/{Tabs → components/Tabs}/index.vue +30 -4
  145. package/dist/v3/{Tag → components/Tag}/index.vue +113 -113
  146. package/dist/v3/{VirtualList → components/VirtualList}/AutoSize.vue +40 -40
  147. package/dist/v3/{VirtualList → components/VirtualList}/index.vue +416 -412
  148. package/dist/v3/hooks/useBluetooth/index.ts +561 -0
  149. package/dist/v3/hooks/useContainerStyle.ts +153 -153
  150. package/dist/v3/hooks/useNFC/index.ts +107 -107
  151. package/dist/v3/hooks/useNFC/typing.d.ts +396 -396
  152. package/dist/v3/hooks/useNFC/useNFCAndroid.ts +966 -966
  153. package/dist/v3/hooks/useNFC/useNFCMpWeiXin.ts +812 -812
  154. package/dist/v3/hooks/useNFC/utils.ts +754 -754
  155. package/dist/v3/hooks/useRequest/index.ts +586 -573
  156. package/dist/v3/hooks/useRequest/utils.ts +267 -267
  157. package/dist/v3/hooks/useScanCode/index.ts +206 -206
  158. package/dist/v3/hooks/useWebsocket/README.md +79 -0
  159. package/dist/v3/hooks/useWebsocket/index.ts +253 -0
  160. package/dist/v3/styles/theme.css +17 -10
  161. package/dist/v3/styles/theme.ts +12 -12
  162. package/package.json +59 -64
  163. package/dist/plugins/uniParseStock/index.d.ts +0 -10
  164. package/dist/plugins/uniParseStock/index.d.ts.map +0 -1
  165. /package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/index.css +0 -0
  166. /package/dist/v3/{SettingList → DevTool/components/SettingList}/index.css +0 -0
  167. /package/dist/v3/{Empty → components/Empty}/empty.png +0 -0
  168. /package/dist/v3/{VirtualList → components/VirtualList}/readme.md +0 -0
@@ -1,460 +1,460 @@
1
- import { ref, computed, nextTick } from 'vue';
2
- import { raf, caf } from '../../../utils';
3
-
4
- export function usePerformanceChart(opts: {
5
- theme: any;
6
- isDark: any;
7
- instance: any;
8
- canvasIdFn: () => string;
9
- stepSize: number;
10
- data: any; // from usePerformanceData
11
- config: {
12
- showFps: any;
13
- showCpu: any;
14
- showMemory: any;
15
- };
16
- props: any;
17
- }) {
18
- const canvasWidth = ref(0);
19
- const canvasHeight = ref(0);
20
- let canvasCtx: UniApp.CanvasContext | null = null;
21
- let renderTimer: number | null = null;
22
- // 使用标志变量控制渲染循环,确保在 Android 平台能正确停止
23
- let isRunning = false;
24
-
25
- // Colors
26
- const textColor = computed(() => {
27
- if (opts.theme.value === 'eye') return '#586e75';
28
- return opts.isDark.value ? '#ffffff' : '#000000';
29
- });
30
- const chartBgColor = computed(() => {
31
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.05)';
32
- return opts.isDark.value
33
- ? 'rgba(255, 255, 255, 0.08)'
34
- : 'rgba(0, 0, 0, 0.05)';
35
- });
36
- const mainLineColor = computed(() => {
37
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.3)';
38
- return opts.isDark.value
39
- ? 'rgba(255, 255, 255, 0.4)'
40
- : 'rgba(0, 0, 0, 0.2)';
41
- });
42
- const subLineColor = computed(() => {
43
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.1)';
44
- return opts.isDark.value
45
- ? 'rgba(255, 255, 255, 0.15)'
46
- : 'rgba(0, 0, 0, 0.08)';
47
- });
48
-
49
- function initCanvas(onReady?: () => void) {
50
- const canvasId = opts.canvasIdFn();
51
- canvasCtx = uni.createCanvasContext(canvasId, opts.instance?.proxy);
52
-
53
- const queryCanvas = (retryCount = 0) => {
54
- uni
55
- .createSelectorQuery()
56
- .in(opts.instance?.proxy)
57
- .select(`#${canvasId}`)
58
- .boundingClientRect((rect) => {
59
- const r = rect as any;
60
- if (r && r.width > 0 && r.height > 0) {
61
- canvasWidth.value = r.width;
62
- canvasHeight.value = r.height;
63
-
64
- // Initialize data if needed (call initDate from usePerformanceData)
65
- const points = Math.ceil(canvasWidth.value / opts.stepSize) + 4;
66
- opts.data.initData(points);
67
-
68
- if (onReady) onReady();
69
- } else {
70
- // Android 平台在二次打开时可能需要多一点时间才能获取到正确的宽高
71
- // 增加重试机制:每 50ms 重试一次,最多重试 10 次 (500ms)
72
- if (retryCount < 10) {
73
- setTimeout(() => {
74
- queryCanvas(retryCount + 1);
75
- }, 50);
76
- } else {
77
- console.warn(
78
- `[UniDevTool] Failed to get canvas rect for ${canvasId}`,
79
- );
80
- }
81
- }
82
- })
83
- .exec();
84
- };
85
-
86
- nextTick(() => {
87
- // 首次尝试稍微延迟一点,避开极其短暂的中间状态
88
- setTimeout(() => queryCanvas(), 50);
89
- });
90
- }
91
-
92
- function startRenderLoop() {
93
- // 避免重复启动
94
- if (isRunning) return;
95
- isRunning = true;
96
-
97
- const render = () => {
98
- // 检查标志位,如果已停止则不再继续
99
- if (!isRunning) return;
100
- drawChart();
101
- renderTimer = raf(render);
102
- };
103
- render();
104
- }
105
-
106
- function stopRenderLoop() {
107
- isRunning = false;
108
- if (renderTimer) {
109
- caf(renderTimer);
110
- renderTimer = null;
111
- }
112
- }
113
-
114
- function resetCanvas() {
115
- stopRenderLoop();
116
- canvasCtx = null;
117
- canvasWidth.value = 0;
118
- canvasHeight.value = 0;
119
- }
120
-
121
- function drawChart() {
122
- if (!canvasCtx || !canvasWidth.value || !canvasHeight.value) return;
123
-
124
- const ctx = canvasCtx;
125
- const w = canvasWidth.value;
126
- const h = canvasHeight.value;
127
- const bottomPadding = 12;
128
- const drawH = h - bottomPadding;
129
-
130
- // Data aliases
131
- const {
132
- fpsHistory: fList,
133
- cpuHistory: cList,
134
- memoryHistory: mList,
135
- timeHistory: tList,
136
- displayMaxScale: currentScale,
137
- } = opts.data;
138
-
139
- // Props aliases
140
- const config = opts.config;
141
- const props = opts.props;
142
-
143
- // 计算平滑偏移量
144
- const now = Date.now();
145
-
146
- // 强制使用当前时间作为最右侧锚点
147
- // 这样数据永远不会往右移动,只会随着时间推移自然向左滚动
148
- const rightEdgeTime = now;
149
-
150
- const getX = (ts: number) => {
151
- if (!ts) return -100; // 兜底处理无效数据
152
- return w - ((rightEdgeTime - ts) / 1000) * opts.stepSize;
153
- };
154
-
155
- const activeMetrics = [
156
- { key: 'cpu', show: config.showCpu.value },
157
- { key: 'memory', show: config.showMemory.value },
158
- { key: 'fps', show: config.showFps.value },
159
- ].filter((m) => m.show);
160
-
161
- const count = activeMetrics.length;
162
- const isSplit = count > 1;
163
- const chartHeight = count > 0 ? drawH / count : drawH;
164
-
165
- let cpuY = 0;
166
- let memY = 0;
167
- let fpsY = 0;
168
-
169
- activeMetrics.forEach((m, i) => {
170
- if (m.key === 'cpu') cpuY = i * chartHeight;
171
- if (m.key === 'memory') memY = i * chartHeight;
172
- if (m.key === 'fps') fpsY = i * chartHeight;
173
- });
174
-
175
- // 1. 绘制背景网格
176
- const maxFps = Math.max(...fList.value, 0);
177
- let targetScale = 60;
178
- if (maxFps > 144) targetScale = 240;
179
- else if (maxFps > 120) targetScale = 144;
180
- else if (maxFps > 90) targetScale = 120;
181
-
182
- const getStandardPoints = (scale: number) => {
183
- return [0, 30, 60, 100, 144, 240].filter(
184
- (p) => p <= Math.max(scale, currentScale.value),
185
- );
186
- };
187
-
188
- ctx.setLineWidth(1);
189
- ctx.setFontSize(props.mode === 'widget' ? 10 : 12);
190
-
191
- // --- FPS ---
192
- if (config.showFps.value) {
193
- const fpsPoints = getStandardPoints(targetScale);
194
- fpsPoints.forEach((p) => {
195
- const yPos =
196
- fpsY + (chartHeight - (p / currentScale.value) * chartHeight);
197
- if (yPos < fpsY - 5 || yPos > fpsY + chartHeight + 5) return;
198
-
199
- ctx.beginPath();
200
- ctx.setStrokeStyle(
201
- p === 0 || p === 60 || p === targetScale
202
- ? mainLineColor.value
203
- : subLineColor.value,
204
- );
205
- ctx.setLineDash(
206
- p === 0 || p === 60 || p === targetScale ? [] : [2, 4],
207
- 0,
208
- );
209
- ctx.moveTo(0, yPos);
210
- ctx.lineTo(w, yPos);
211
- ctx.stroke();
212
-
213
- ctx.setFillStyle(textColor.value);
214
- if (p !== 0 && (p === 60 || p === targetScale)) {
215
- ctx.setTextBaseline(p === targetScale ? 'top' : 'bottom');
216
- ctx.fillText(`${p}${isSplit ? ' FPS' : ''}`, 0, yPos);
217
- }
218
- });
219
- }
220
-
221
- // --- CPU ---
222
- if (config.showCpu.value) {
223
- const cpuPoints = [0, 50, 100];
224
- cpuPoints.forEach((p) => {
225
- const yPos = cpuY + (chartHeight - (p / 100) * chartHeight);
226
- if (yPos < cpuY - 5 || yPos > cpuY + chartHeight + 5) return;
227
-
228
- if (!isSplit || p !== 0) {
229
- ctx.beginPath();
230
- ctx.setStrokeStyle(
231
- p === 0 || p === 100 ? mainLineColor.value : subLineColor.value,
232
- );
233
- ctx.setLineDash(p === 0 || p === 100 ? [] : [2, 4], 0);
234
- ctx.moveTo(0, yPos);
235
- ctx.lineTo(w, yPos);
236
- ctx.stroke();
237
- }
238
-
239
- ctx.setFillStyle(textColor.value);
240
- if (p !== 0) {
241
- ctx.setTextBaseline(p === 100 ? 'top' : 'middle');
242
- ctx.fillText(`${p}${isSplit ? '%' : ''}`, 0, yPos);
243
- }
244
- });
245
- }
246
-
247
- // --- Memory ---
248
- const maxMem = Math.max(...mList.value, 100);
249
- let targetMemScale = Math.ceil(maxMem / 100) * 100;
250
- if (targetMemScale === 0) targetMemScale = 100;
251
-
252
- if (config.showMemory.value) {
253
- const memPoints = [0, targetMemScale / 2, targetMemScale];
254
- memPoints.forEach((p) => {
255
- const yPos = memY + (chartHeight - (p / targetMemScale) * chartHeight);
256
- if (yPos < memY - 5 || yPos > memY + chartHeight + 5) return;
257
-
258
- if (!isSplit || p !== 0) {
259
- ctx.beginPath();
260
- ctx.setStrokeStyle(
261
- p === 0 || p === targetMemScale
262
- ? mainLineColor.value
263
- : subLineColor.value,
264
- );
265
- ctx.setLineDash(p === 0 || p === targetMemScale ? [] : [2, 4], 0);
266
- ctx.moveTo(0, yPos);
267
- ctx.lineTo(w, yPos);
268
- ctx.stroke();
269
- }
270
-
271
- ctx.setFillStyle(textColor.value);
272
- if (p !== 0) {
273
- ctx.setTextBaseline(p === targetMemScale ? 'top' : 'middle');
274
- ctx.fillText(`${p.toFixed(0)}${isSplit ? 'M' : ''}`, 0, yPos);
275
- }
276
- });
277
- }
278
-
279
- // Split Lines
280
- if (isSplit) {
281
- ctx.setStrokeStyle(mainLineColor.value);
282
- ctx.setLineDash([], 0);
283
- for (let i = 1; i < count; i++) {
284
- const y = chartHeight * i;
285
- ctx.beginPath();
286
- ctx.moveTo(0, y);
287
- ctx.lineTo(w, y);
288
- ctx.stroke();
289
- }
290
- }
291
-
292
- // Vertical Grid & Time Layout
293
- const timeList = tList.value;
294
-
295
- // Vertical Grid - 基于时间绘制,不再基于数组索引,保证网格绝对均匀
296
- ctx.setStrokeStyle(subLineColor.value);
297
- ctx.setLineDash([2, 4], 0);
298
-
299
- const startTimeOnScreen = rightEdgeTime - (w / opts.stepSize) * 1000;
300
- const startGridSec = Math.ceil(startTimeOnScreen / 5000) * 5000;
301
-
302
- for (let t = startGridSec; t <= rightEdgeTime; t += 5000) {
303
- const xPos = getX(t);
304
- if (xPos >= 0 && xPos <= w) {
305
- ctx.beginPath();
306
- ctx.moveTo(xPos, 0);
307
- ctx.lineTo(xPos, drawH);
308
- ctx.stroke();
309
- }
310
- }
311
-
312
- // Time Axis
313
- if (timeList.length > 0) {
314
- ctx.setLineDash([], 0);
315
- ctx.setStrokeStyle(mainLineColor.value);
316
- ctx.beginPath();
317
- ctx.moveTo(0, drawH);
318
- ctx.lineTo(w, drawH);
319
- ctx.stroke();
320
-
321
- ctx.setFillStyle(textColor.value);
322
- ctx.setFontSize(10);
323
- ctx.setTextAlign('center');
324
-
325
- const formatTime = (ts: number) => {
326
- const d = new Date(ts);
327
- return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
328
- };
329
-
330
- const startTimeLabel = Math.ceil(startTimeOnScreen / 15000) * 15000;
331
- for (let t = startTimeLabel; t <= rightEdgeTime; t += 15000) {
332
- const xPos = getX(t);
333
- if (xPos >= 0 && xPos <= w) {
334
- const timeStr = formatTime(t);
335
- ctx.setTextBaseline('bottom');
336
- ctx.fillText(timeStr, xPos, h);
337
- }
338
- }
339
- ctx.setTextAlign('left');
340
- }
341
-
342
- const drawAreaChart = (
343
- list: number[],
344
- startY: number,
345
- height: number,
346
- maxVal: number,
347
- color: string,
348
- gradientStart: string,
349
- gradientEnd: string,
350
- ) => {
351
- const bottomY = startY + height;
352
- if (list.length === 0) return;
353
-
354
- const getValY = (val: number) =>
355
- startY + (height - (val / maxVal) * height);
356
-
357
- // 1. 准备渐变
358
- const gradient = ctx.createLinearGradient(0, startY, 0, bottomY);
359
- gradient.addColorStop(0, gradientStart);
360
- gradient.addColorStop(1, gradientEnd);
361
- ctx.setFillStyle(gradient);
362
-
363
- // 2. 绘制填充面积
364
- ctx.beginPath();
365
- ctx.setLineDash([], 0);
366
-
367
- const x0 = getX(timeList[0]);
368
- const y0 = getValY(list[0]);
369
- ctx.moveTo(x0, bottomY);
370
- ctx.lineTo(x0, y0);
371
-
372
- let lastY = y0;
373
- for (let i = 1; i < list.length; i++) {
374
- const x = getX(timeList[i]);
375
- lastY = getValY(list[i]);
376
- ctx.lineTo(x, lastY);
377
- }
378
-
379
- // 延伸到右侧边缘
380
- ctx.lineTo(w, lastY);
381
- ctx.lineTo(w, bottomY);
382
- ctx.closePath();
383
- ctx.fill();
384
-
385
- // 3. 绘制顶部线条
386
- ctx.beginPath();
387
- ctx.setLineWidth(2);
388
- ctx.setStrokeStyle(color);
389
- ctx.moveTo(x0, y0);
390
- for (let i = 1; i < list.length; i++) {
391
- const x = getX(timeList[i]);
392
- ctx.lineTo(x, getValY(list[i]));
393
- }
394
- ctx.lineTo(w, lastY);
395
- ctx.stroke();
396
- };
397
-
398
- if (config.showCpu.value) {
399
- drawAreaChart(
400
- cList.value,
401
- cpuY,
402
- chartHeight,
403
- 100,
404
- '#9254de',
405
- 'rgba(146, 84, 222, 0.5)',
406
- 'rgba(146, 84, 222, 0.05)',
407
- );
408
- }
409
-
410
- if (config.showMemory.value) {
411
- drawAreaChart(
412
- mList.value,
413
- memY,
414
- chartHeight,
415
- targetMemScale,
416
- '#409eff',
417
- 'rgba(64, 158, 255, 0.5)',
418
- 'rgba(64, 158, 255, 0.05)',
419
- );
420
- }
421
-
422
- if (config.showFps.value) {
423
- const currentFps = fList.value[fList.value.length - 1] || 0;
424
- let colorStr = '#f56c6c';
425
- let gradStart = 'rgba(245, 108, 108, 0.5)';
426
- let gradEnd = 'rgba(245, 108, 108, 0.05)';
427
-
428
- if (currentFps >= 50) {
429
- colorStr = '#67c23a';
430
- gradStart = 'rgba(103, 194, 58, 0.5)';
431
- gradEnd = 'rgba(103, 194, 58, 0.05)';
432
- } else if (currentFps >= 30) {
433
- colorStr = '#e6a23c';
434
- gradStart = 'rgba(230, 162, 60, 0.5)';
435
- gradEnd = 'rgba(230, 162, 60, 0.05)';
436
- }
437
-
438
- drawAreaChart(
439
- fList.value,
440
- fpsY,
441
- chartHeight,
442
- currentScale.value,
443
- colorStr,
444
- gradStart,
445
- gradEnd,
446
- );
447
- }
448
-
449
- ctx.draw();
450
- }
451
-
452
- return {
453
- chartBgColor,
454
- canvasWidth,
455
- initCanvas,
456
- startRenderLoop,
457
- stopRenderLoop,
458
- resetCanvas,
459
- };
460
- }
1
+ import { ref, computed, nextTick } from 'vue';
2
+ import { raf, caf } from '../../../../../utils';
3
+
4
+ export function usePerformanceChart(opts: {
5
+ theme: any;
6
+ isDark: any;
7
+ instance: any;
8
+ canvasIdFn: () => string;
9
+ stepSize: number;
10
+ data: any; // from usePerformanceData
11
+ config: {
12
+ showFps: any;
13
+ showCpu: any;
14
+ showMemory: any;
15
+ };
16
+ props: any;
17
+ }) {
18
+ const canvasWidth = ref(0);
19
+ const canvasHeight = ref(0);
20
+ let canvasCtx: UniApp.CanvasContext | null = null;
21
+ let renderTimer: number | null = null;
22
+ // 使用标志变量控制渲染循环,确保在 Android 平台能正确停止
23
+ let isRunning = false;
24
+
25
+ // Colors
26
+ const textColor = computed(() => {
27
+ if (opts.theme.value === 'eye') return '#586e75';
28
+ return opts.isDark.value ? '#ffffff' : '#000000';
29
+ });
30
+ const chartBgColor = computed(() => {
31
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.05)';
32
+ return opts.isDark.value
33
+ ? 'rgba(255, 255, 255, 0.08)'
34
+ : 'rgba(0, 0, 0, 0.05)';
35
+ });
36
+ const mainLineColor = computed(() => {
37
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.3)';
38
+ return opts.isDark.value
39
+ ? 'rgba(255, 255, 255, 0.4)'
40
+ : 'rgba(0, 0, 0, 0.2)';
41
+ });
42
+ const subLineColor = computed(() => {
43
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.1)';
44
+ return opts.isDark.value
45
+ ? 'rgba(255, 255, 255, 0.15)'
46
+ : 'rgba(0, 0, 0, 0.08)';
47
+ });
48
+
49
+ function initCanvas(onReady?: () => void) {
50
+ const canvasId = opts.canvasIdFn();
51
+ canvasCtx = uni.createCanvasContext(canvasId, opts.instance?.proxy);
52
+
53
+ const queryCanvas = (retryCount = 0) => {
54
+ uni
55
+ .createSelectorQuery()
56
+ .in(opts.instance?.proxy)
57
+ .select(`#${canvasId}`)
58
+ .boundingClientRect((rect) => {
59
+ const r = rect as any;
60
+ if (r && r.width > 0 && r.height > 0) {
61
+ canvasWidth.value = r.width;
62
+ canvasHeight.value = r.height;
63
+
64
+ // Initialize data if needed (call initDate from usePerformanceData)
65
+ const points = Math.ceil(canvasWidth.value / opts.stepSize) + 4;
66
+ opts.data.initData(points);
67
+
68
+ if (onReady) onReady();
69
+ } else {
70
+ // Android 平台在二次打开时可能需要多一点时间才能获取到正确的宽高
71
+ // 增加重试机制:每 50ms 重试一次,最多重试 10 次 (500ms)
72
+ if (retryCount < 10) {
73
+ setTimeout(() => {
74
+ queryCanvas(retryCount + 1);
75
+ }, 50);
76
+ } else {
77
+ console.warn(
78
+ `[UniDevTool] Failed to get canvas rect for ${canvasId}`,
79
+ );
80
+ }
81
+ }
82
+ })
83
+ .exec();
84
+ };
85
+
86
+ nextTick(() => {
87
+ // 首次尝试稍微延迟一点,避开极其短暂的中间状态
88
+ setTimeout(() => queryCanvas(), 50);
89
+ });
90
+ }
91
+
92
+ function startRenderLoop() {
93
+ // 避免重复启动
94
+ if (isRunning) return;
95
+ isRunning = true;
96
+
97
+ const render = () => {
98
+ // 检查标志位,如果已停止则不再继续
99
+ if (!isRunning) return;
100
+ drawChart();
101
+ renderTimer = raf(render);
102
+ };
103
+ render();
104
+ }
105
+
106
+ function stopRenderLoop() {
107
+ isRunning = false;
108
+ if (renderTimer) {
109
+ caf(renderTimer);
110
+ renderTimer = null;
111
+ }
112
+ }
113
+
114
+ function resetCanvas() {
115
+ stopRenderLoop();
116
+ canvasCtx = null;
117
+ canvasWidth.value = 0;
118
+ canvasHeight.value = 0;
119
+ }
120
+
121
+ function drawChart() {
122
+ if (!canvasCtx || !canvasWidth.value || !canvasHeight.value) return;
123
+
124
+ const ctx = canvasCtx;
125
+ const w = canvasWidth.value;
126
+ const h = canvasHeight.value;
127
+ const bottomPadding = 12;
128
+ const drawH = h - bottomPadding;
129
+
130
+ // Data aliases
131
+ const {
132
+ fpsHistory: fList,
133
+ cpuHistory: cList,
134
+ memoryHistory: mList,
135
+ timeHistory: tList,
136
+ displayMaxScale: currentScale,
137
+ } = opts.data;
138
+
139
+ // Props aliases
140
+ const config = opts.config;
141
+ const props = opts.props;
142
+
143
+ // 计算平滑偏移量
144
+ const now = Date.now();
145
+
146
+ // 强制使用当前时间作为最右侧锚点
147
+ // 这样数据永远不会往右移动,只会随着时间推移自然向左滚动
148
+ const rightEdgeTime = now;
149
+
150
+ const getX = (ts: number) => {
151
+ if (!ts) return -100; // 兜底处理无效数据
152
+ return w - ((rightEdgeTime - ts) / 1000) * opts.stepSize;
153
+ };
154
+
155
+ const activeMetrics = [
156
+ { key: 'cpu', show: config.showCpu.value },
157
+ { key: 'memory', show: config.showMemory.value },
158
+ { key: 'fps', show: config.showFps.value },
159
+ ].filter((m) => m.show);
160
+
161
+ const count = activeMetrics.length;
162
+ const isSplit = count > 1;
163
+ const chartHeight = count > 0 ? drawH / count : drawH;
164
+
165
+ let cpuY = 0;
166
+ let memY = 0;
167
+ let fpsY = 0;
168
+
169
+ activeMetrics.forEach((m, i) => {
170
+ if (m.key === 'cpu') cpuY = i * chartHeight;
171
+ if (m.key === 'memory') memY = i * chartHeight;
172
+ if (m.key === 'fps') fpsY = i * chartHeight;
173
+ });
174
+
175
+ // 1. 绘制背景网格
176
+ const maxFps = Math.max(...fList.value, 0);
177
+ let targetScale = 60;
178
+ if (maxFps > 144) targetScale = 240;
179
+ else if (maxFps > 120) targetScale = 144;
180
+ else if (maxFps > 90) targetScale = 120;
181
+
182
+ const getStandardPoints = (scale: number) => {
183
+ return [0, 30, 60, 100, 144, 240].filter(
184
+ (p) => p <= Math.max(scale, currentScale.value),
185
+ );
186
+ };
187
+
188
+ ctx.setLineWidth(1);
189
+ ctx.setFontSize(props.mode === 'widget' ? 10 : 12);
190
+
191
+ // --- FPS ---
192
+ if (config.showFps.value) {
193
+ const fpsPoints = getStandardPoints(targetScale);
194
+ fpsPoints.forEach((p) => {
195
+ const yPos =
196
+ fpsY + (chartHeight - (p / currentScale.value) * chartHeight);
197
+ if (yPos < fpsY - 5 || yPos > fpsY + chartHeight + 5) return;
198
+
199
+ ctx.beginPath();
200
+ ctx.setStrokeStyle(
201
+ p === 0 || p === 60 || p === targetScale
202
+ ? mainLineColor.value
203
+ : subLineColor.value,
204
+ );
205
+ ctx.setLineDash(
206
+ p === 0 || p === 60 || p === targetScale ? [] : [2, 4],
207
+ 0,
208
+ );
209
+ ctx.moveTo(0, yPos);
210
+ ctx.lineTo(w, yPos);
211
+ ctx.stroke();
212
+
213
+ ctx.setFillStyle(textColor.value);
214
+ if (p !== 0 && (p === 60 || p === targetScale)) {
215
+ ctx.setTextBaseline(p === targetScale ? 'top' : 'bottom');
216
+ ctx.fillText(`${p}${isSplit ? ' FPS' : ''}`, 0, yPos);
217
+ }
218
+ });
219
+ }
220
+
221
+ // --- CPU ---
222
+ if (config.showCpu.value) {
223
+ const cpuPoints = [0, 50, 100];
224
+ cpuPoints.forEach((p) => {
225
+ const yPos = cpuY + (chartHeight - (p / 100) * chartHeight);
226
+ if (yPos < cpuY - 5 || yPos > cpuY + chartHeight + 5) return;
227
+
228
+ if (!isSplit || p !== 0) {
229
+ ctx.beginPath();
230
+ ctx.setStrokeStyle(
231
+ p === 0 || p === 100 ? mainLineColor.value : subLineColor.value,
232
+ );
233
+ ctx.setLineDash(p === 0 || p === 100 ? [] : [2, 4], 0);
234
+ ctx.moveTo(0, yPos);
235
+ ctx.lineTo(w, yPos);
236
+ ctx.stroke();
237
+ }
238
+
239
+ ctx.setFillStyle(textColor.value);
240
+ if (p !== 0) {
241
+ ctx.setTextBaseline(p === 100 ? 'top' : 'middle');
242
+ ctx.fillText(`${p}${isSplit ? '%' : ''}`, 0, yPos);
243
+ }
244
+ });
245
+ }
246
+
247
+ // --- Memory ---
248
+ const maxMem = Math.max(...mList.value, 100);
249
+ let targetMemScale = Math.ceil(maxMem / 100) * 100;
250
+ if (targetMemScale === 0) targetMemScale = 100;
251
+
252
+ if (config.showMemory.value) {
253
+ const memPoints = [0, targetMemScale / 2, targetMemScale];
254
+ memPoints.forEach((p) => {
255
+ const yPos = memY + (chartHeight - (p / targetMemScale) * chartHeight);
256
+ if (yPos < memY - 5 || yPos > memY + chartHeight + 5) return;
257
+
258
+ if (!isSplit || p !== 0) {
259
+ ctx.beginPath();
260
+ ctx.setStrokeStyle(
261
+ p === 0 || p === targetMemScale
262
+ ? mainLineColor.value
263
+ : subLineColor.value,
264
+ );
265
+ ctx.setLineDash(p === 0 || p === targetMemScale ? [] : [2, 4], 0);
266
+ ctx.moveTo(0, yPos);
267
+ ctx.lineTo(w, yPos);
268
+ ctx.stroke();
269
+ }
270
+
271
+ ctx.setFillStyle(textColor.value);
272
+ if (p !== 0) {
273
+ ctx.setTextBaseline(p === targetMemScale ? 'top' : 'middle');
274
+ ctx.fillText(`${p.toFixed(0)}${isSplit ? 'M' : ''}`, 0, yPos);
275
+ }
276
+ });
277
+ }
278
+
279
+ // Split Lines
280
+ if (isSplit) {
281
+ ctx.setStrokeStyle(mainLineColor.value);
282
+ ctx.setLineDash([], 0);
283
+ for (let i = 1; i < count; i++) {
284
+ const y = chartHeight * i;
285
+ ctx.beginPath();
286
+ ctx.moveTo(0, y);
287
+ ctx.lineTo(w, y);
288
+ ctx.stroke();
289
+ }
290
+ }
291
+
292
+ // Vertical Grid & Time Layout
293
+ const timeList = tList.value;
294
+
295
+ // Vertical Grid - 基于时间绘制,不再基于数组索引,保证网格绝对均匀
296
+ ctx.setStrokeStyle(subLineColor.value);
297
+ ctx.setLineDash([2, 4], 0);
298
+
299
+ const startTimeOnScreen = rightEdgeTime - (w / opts.stepSize) * 1000;
300
+ const startGridSec = Math.ceil(startTimeOnScreen / 5000) * 5000;
301
+
302
+ for (let t = startGridSec; t <= rightEdgeTime; t += 5000) {
303
+ const xPos = getX(t);
304
+ if (xPos >= 0 && xPos <= w) {
305
+ ctx.beginPath();
306
+ ctx.moveTo(xPos, 0);
307
+ ctx.lineTo(xPos, drawH);
308
+ ctx.stroke();
309
+ }
310
+ }
311
+
312
+ // Time Axis
313
+ if (timeList.length > 0) {
314
+ ctx.setLineDash([], 0);
315
+ ctx.setStrokeStyle(mainLineColor.value);
316
+ ctx.beginPath();
317
+ ctx.moveTo(0, drawH);
318
+ ctx.lineTo(w, drawH);
319
+ ctx.stroke();
320
+
321
+ ctx.setFillStyle(textColor.value);
322
+ ctx.setFontSize(10);
323
+ ctx.setTextAlign('center');
324
+
325
+ const formatTime = (ts: number) => {
326
+ const d = new Date(ts);
327
+ return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
328
+ };
329
+
330
+ const startTimeLabel = Math.ceil(startTimeOnScreen / 15000) * 15000;
331
+ for (let t = startTimeLabel; t <= rightEdgeTime; t += 15000) {
332
+ const xPos = getX(t);
333
+ if (xPos >= 0 && xPos <= w) {
334
+ const timeStr = formatTime(t);
335
+ ctx.setTextBaseline('bottom');
336
+ ctx.fillText(timeStr, xPos, h);
337
+ }
338
+ }
339
+ ctx.setTextAlign('left');
340
+ }
341
+
342
+ const drawAreaChart = (
343
+ list: number[],
344
+ startY: number,
345
+ height: number,
346
+ maxVal: number,
347
+ color: string,
348
+ gradientStart: string,
349
+ gradientEnd: string,
350
+ ) => {
351
+ const bottomY = startY + height;
352
+ if (list.length === 0) return;
353
+
354
+ const getValY = (val: number) =>
355
+ startY + (height - (val / maxVal) * height);
356
+
357
+ // 1. 准备渐变
358
+ const gradient = ctx.createLinearGradient(0, startY, 0, bottomY);
359
+ gradient.addColorStop(0, gradientStart);
360
+ gradient.addColorStop(1, gradientEnd);
361
+ ctx.setFillStyle(gradient);
362
+
363
+ // 2. 绘制填充面积
364
+ ctx.beginPath();
365
+ ctx.setLineDash([], 0);
366
+
367
+ const x0 = getX(timeList[0]);
368
+ const y0 = getValY(list[0]);
369
+ ctx.moveTo(x0, bottomY);
370
+ ctx.lineTo(x0, y0);
371
+
372
+ let lastY = y0;
373
+ for (let i = 1; i < list.length; i++) {
374
+ const x = getX(timeList[i]);
375
+ lastY = getValY(list[i]);
376
+ ctx.lineTo(x, lastY);
377
+ }
378
+
379
+ // 延伸到右侧边缘
380
+ ctx.lineTo(w, lastY);
381
+ ctx.lineTo(w, bottomY);
382
+ ctx.closePath();
383
+ ctx.fill();
384
+
385
+ // 3. 绘制顶部线条
386
+ ctx.beginPath();
387
+ ctx.setLineWidth(2);
388
+ ctx.setStrokeStyle(color);
389
+ ctx.moveTo(x0, y0);
390
+ for (let i = 1; i < list.length; i++) {
391
+ const x = getX(timeList[i]);
392
+ ctx.lineTo(x, getValY(list[i]));
393
+ }
394
+ ctx.lineTo(w, lastY);
395
+ ctx.stroke();
396
+ };
397
+
398
+ if (config.showCpu.value) {
399
+ drawAreaChart(
400
+ cList.value,
401
+ cpuY,
402
+ chartHeight,
403
+ 100,
404
+ '#9254de',
405
+ 'rgba(146, 84, 222, 0.5)',
406
+ 'rgba(146, 84, 222, 0.05)',
407
+ );
408
+ }
409
+
410
+ if (config.showMemory.value) {
411
+ drawAreaChart(
412
+ mList.value,
413
+ memY,
414
+ chartHeight,
415
+ targetMemScale,
416
+ '#409eff',
417
+ 'rgba(64, 158, 255, 0.5)',
418
+ 'rgba(64, 158, 255, 0.05)',
419
+ );
420
+ }
421
+
422
+ if (config.showFps.value) {
423
+ const currentFps = fList.value[fList.value.length - 1] || 0;
424
+ let colorStr = '#f56c6c';
425
+ let gradStart = 'rgba(245, 108, 108, 0.5)';
426
+ let gradEnd = 'rgba(245, 108, 108, 0.05)';
427
+
428
+ if (currentFps >= 50) {
429
+ colorStr = '#67c23a';
430
+ gradStart = 'rgba(103, 194, 58, 0.5)';
431
+ gradEnd = 'rgba(103, 194, 58, 0.05)';
432
+ } else if (currentFps >= 30) {
433
+ colorStr = '#e6a23c';
434
+ gradStart = 'rgba(230, 162, 60, 0.5)';
435
+ gradEnd = 'rgba(230, 162, 60, 0.05)';
436
+ }
437
+
438
+ drawAreaChart(
439
+ fList.value,
440
+ fpsY,
441
+ chartHeight,
442
+ currentScale.value,
443
+ colorStr,
444
+ gradStart,
445
+ gradEnd,
446
+ );
447
+ }
448
+
449
+ ctx.draw();
450
+ }
451
+
452
+ return {
453
+ chartBgColor,
454
+ canvasWidth,
455
+ initCanvas,
456
+ startRenderLoop,
457
+ stopRenderLoop,
458
+ resetCanvas,
459
+ };
460
+ }