185 lines
4.1 KiB
Vue
185 lines
4.1 KiB
Vue
<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>
|