20260724173425
This commit is contained in:
1 parent
4f40ad9f11
commit
b33c743236
27 files changed
+1310
-437
No files matched your search
@@ -0,0 +1,184 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
// FPS 计算采样间隔(毫秒)
|
||||
sampleInterval: { type: Number, default: 500 },
|
||||
// 历史柱状图保留的采样点数
|
||||
historySize: { type: Number, default: 48 },
|
||||
// 柱状图基准 FPS(满高对应值)
|
||||
baseFps: { type: Number, default: 60 },
|
||||
});
|
||||
|
||||
const currentFps = ref(0);
|
||||
const history = ref([]);
|
||||
let rafId = null;
|
||||
let frames = 0;
|
||||
let lastTime = 0;
|
||||
let totalFrames = 0;
|
||||
let totalTime = 0;
|
||||
let minFps = Infinity;
|
||||
|
||||
function tick(now) {
|
||||
frames++;
|
||||
totalFrames++;
|
||||
const elapsed = now - lastTime;
|
||||
if (elapsed >= props.sampleInterval) {
|
||||
const fps = Math.round((frames * 1000) / elapsed);
|
||||
currentFps.value = fps;
|
||||
if (fps < minFps) minFps = fps;
|
||||
totalTime += elapsed;
|
||||
const next =
|
||||
history.value.length >= props.historySize ? history.value.slice(1) : history.value.slice();
|
||||
next.push(fps);
|
||||
history.value = next;
|
||||
frames = 0;
|
||||
lastTime = now;
|
||||
}
|
||||
rafId = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
lastTime = performance.now();
|
||||
rafId = requestAnimationFrame(tick);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (rafId) cancelAnimationFrame(rafId);
|
||||
});
|
||||
|
||||
const avgFps = computed(() => (totalTime > 0 ? Math.round((totalFrames * 1000) / totalTime) : 0));
|
||||
const minFpsDisplay = computed(() => (minFps === Infinity ? "--" : minFps));
|
||||
|
||||
const fpsColor = computed(() => {
|
||||
const f = currentFps.value;
|
||||
if (f >= 50) return "#52c41a";
|
||||
if (f >= 30) return "#faad14";
|
||||
return "#ff4d4f";
|
||||
});
|
||||
|
||||
function barStyle(f) {
|
||||
const h = Math.max(8, Math.min(100, (f / props.baseFps) * 100));
|
||||
const color = f >= 50 ? "#52c41a" : f >= 30 ? "#faad14" : "#ff4d4f";
|
||||
return { height: `${h}%`, background: color };
|
||||
}
|
||||
|
||||
function reset() {
|
||||
history.value = [];
|
||||
totalFrames = 0;
|
||||
totalTime = 0;
|
||||
minFps = Infinity;
|
||||
currentFps.value = 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fps-monitor" v-bind="$attrs">
|
||||
<div class="fps-monitor__head">
|
||||
<span class="fps-monitor__label">FPS</span>
|
||||
<span class="fps-monitor__now" :style="{ color: fpsColor }">{{ currentFps || "--" }}</span>
|
||||
<button class="fps-monitor__reset" type="button" title="重置统计" @click="reset">↻</button>
|
||||
</div>
|
||||
<div class="fps-monitor__chart">
|
||||
<div v-for="(f, i) in history" :key="i" class="fps-monitor__bar" :style="barStyle(f)"></div>
|
||||
</div>
|
||||
<div class="fps-monitor__foot">
|
||||
<span
|
||||
>avg <b>{{ avgFps || "--" }}</b></span
|
||||
>
|
||||
<span
|
||||
>min <b>{{ minFpsDisplay }}</b></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fps-monitor {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 108px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(4px);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fps-monitor__head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.fps-monitor__label {
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.fps-monitor__now {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.fps-monitor__reset {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.fps-monitor__reset:hover {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.fps-monitor__chart {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 1px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.fps-monitor__bar {
|
||||
flex: 1;
|
||||
min-width: 2px;
|
||||
border-radius: 1px;
|
||||
transition:
|
||||
height 0.2s ease,
|
||||
background 0.2s ease;
|
||||
}
|
||||
|
||||
.fps-monitor__foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.fps-monitor__foot b {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user