Skip to content

Commit ae7b5a1

Browse files
author
Bastian Jakobs
committed
feat: enhance performance benchmarking in Tooltip component with detailed metrics
1 parent e858a87 commit ae7b5a1

File tree

1 file changed

+289
-4
lines changed

1 file changed

+289
-4
lines changed

src/components/tooltip/TooltipDirectiveBenchmark.vue

Lines changed: 289 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,172 @@
11
<script setup lang="ts">
2+
import { computed, nextTick, onMounted, ref, watch } from 'vue'
23
import Button from '@/components/Button.vue'
4+
5+
interface PerformanceMetrics {
6+
mountTime: number
7+
memoryUsage: number | null
8+
elementCount: number
9+
renderTime: number
10+
averageTooltipShowTime: number
11+
fps: number
12+
}
13+
14+
const tooltipCount = ref(1000)
15+
const isRendering = ref(false)
16+
const metrics = ref<PerformanceMetrics>({
17+
mountTime: 0,
18+
memoryUsage: null,
19+
elementCount: 0,
20+
renderTime: 0,
21+
averageTooltipShowTime: 0,
22+
fps: 0,
23+
})
24+
25+
const buttons = computed(() =>
26+
Array.from({ length: tooltipCount.value }, (_, i) => `Button ${i + 1}`),
27+
)
28+
29+
// Measure FPS during interaction
30+
let frameCount = 0
31+
let lastFrameTime = performance.now()
32+
let fpsInterval: number | null = null
33+
34+
function measureFPS() {
35+
frameCount++
36+
const currentTime = performance.now()
37+
const elapsed = currentTime - lastFrameTime
38+
39+
if (elapsed >= 1000) {
40+
metrics.value.fps = Math.round((frameCount * 1000) / elapsed)
41+
frameCount = 0
42+
lastFrameTime = currentTime
43+
}
44+
45+
if (fpsInterval !== null) {
46+
requestAnimationFrame(measureFPS)
47+
}
48+
}
49+
50+
function startFPSMonitoring() {
51+
if (fpsInterval === null) {
52+
frameCount = 0
53+
lastFrameTime = performance.now()
54+
fpsInterval = requestAnimationFrame(measureFPS) as unknown as number
55+
}
56+
}
57+
58+
function stopFPSMonitoring() {
59+
if (fpsInterval !== null) {
60+
cancelAnimationFrame(fpsInterval)
61+
fpsInterval = null
62+
}
63+
}
64+
65+
// Measure tooltip show time
66+
async function measureTooltipShowTime(): Promise<number> {
67+
const testElement = document.querySelector('[data-benchmark-test]') as HTMLElement
68+
if (!testElement)
69+
return 0
70+
71+
const times: number[] = []
72+
73+
for (let i = 0; i < 10; i++) {
74+
const startTime = performance.now()
75+
76+
// Trigger tooltip
77+
const mouseEnterEvent = new MouseEvent('mouseenter', {
78+
bubbles: true,
79+
cancelable: true,
80+
})
81+
testElement.dispatchEvent(mouseEnterEvent)
82+
83+
await nextTick()
84+
await new Promise(resolve => setTimeout(resolve, 100))
85+
86+
const endTime = performance.now()
87+
times.push(endTime - startTime)
88+
89+
// Hide tooltip
90+
const mouseLeaveEvent = new MouseEvent('mouseleave', {
91+
bubbles: true,
92+
cancelable: true,
93+
})
94+
testElement.dispatchEvent(mouseLeaveEvent)
95+
96+
await new Promise(resolve => setTimeout(resolve, 50))
97+
}
98+
99+
return times.reduce((a, b) => a + b, 0) / times.length
100+
}
101+
102+
// Measure rendering performance
103+
async function measurePerformance() {
104+
isRendering.value = true
105+
106+
const startTime = performance.now()
107+
108+
// Force re-render
109+
const currentCount = tooltipCount.value
110+
tooltipCount.value = 0
111+
await nextTick()
112+
113+
tooltipCount.value = currentCount
114+
await nextTick()
115+
116+
const mountEndTime = performance.now()
117+
metrics.value.mountTime = mountEndTime - startTime
118+
119+
// Wait for DOM to settle
120+
await new Promise(resolve => setTimeout(resolve, 100))
121+
122+
const renderEndTime = performance.now()
123+
metrics.value.renderTime = renderEndTime - startTime
124+
125+
// Count elements
126+
const container = document.querySelector('[data-benchmark-container]')
127+
metrics.value.elementCount = container ? container.querySelectorAll('button').length : 0
128+
129+
// Measure memory (if available)
130+
if ((performance as any).memory) {
131+
metrics.value.memoryUsage = Math.round(
132+
(performance as any).memory.usedJSHeapSize / 1048576,
133+
)
134+
}
135+
136+
// Measure tooltip show time
137+
metrics.value.averageTooltipShowTime = await measureTooltipShowTime()
138+
139+
isRendering.value = false
140+
}
141+
142+
// Run benchmark on mount and when count changes
143+
onMounted(async () => {
144+
await nextTick()
145+
await measurePerformance()
146+
startFPSMonitoring()
147+
})
148+
149+
watch(tooltipCount, async () => {
150+
if (!isRendering.value) {
151+
await measurePerformance()
152+
}
153+
})
154+
155+
// Cleanup
156+
onMounted(() => {
157+
return () => {
158+
stopFPSMonitoring()
159+
}
160+
})
161+
162+
function handleMouseEnter() {
163+
startFPSMonitoring()
164+
}
165+
166+
function handleMouseLeave() {
167+
stopFPSMonitoring()
168+
metrics.value.fps = 0
169+
}
3170
</script>
4171

5172
<template>
@@ -8,17 +175,135 @@ import Button from '@/components/Button.vue'
8175
Tooltip Directive Benchmark
9176
</h2>
10177

11-
<!-- Performance test with 100 small buttons with tooltips -->
178+
<!-- Performance Metrics Dashboard -->
179+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 border border-gray-200 dark:border-gray-700">
180+
<h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-gray-200">
181+
Performance Metrics
182+
</h3>
183+
184+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
185+
<!-- Tooltip Count -->
186+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
187+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
188+
Tooltip Count
189+
</div>
190+
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
191+
{{ metrics.elementCount }}
192+
</div>
193+
</div>
194+
195+
<!-- Mount Time -->
196+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
197+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
198+
Mount Time
199+
</div>
200+
<div class="text-2xl font-bold text-green-600 dark:text-green-400">
201+
{{ metrics.mountTime.toFixed(2) }}ms
202+
</div>
203+
</div>
204+
205+
<!-- Render Time -->
206+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
207+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
208+
Total Render Time
209+
</div>
210+
<div class="text-2xl font-bold text-purple-600 dark:text-purple-400">
211+
{{ metrics.renderTime.toFixed(2) }}ms
212+
</div>
213+
</div>
214+
215+
<!-- Average Tooltip Show Time -->
216+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
217+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
218+
Avg. Show Time
219+
</div>
220+
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">
221+
{{ metrics.averageTooltipShowTime.toFixed(2) }}ms
222+
</div>
223+
</div>
224+
225+
<!-- FPS -->
226+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
227+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
228+
FPS (hover to measure)
229+
</div>
230+
<div
231+
class="text-2xl font-bold"
232+
:class="{
233+
'text-green-600 dark:text-green-400': metrics.fps >= 55,
234+
'text-yellow-600 dark:text-yellow-400': metrics.fps >= 30 && metrics.fps < 55,
235+
'text-red-600 dark:text-red-400': metrics.fps > 0 && metrics.fps < 30,
236+
'text-gray-600 dark:text-gray-400': metrics.fps === 0,
237+
}"
238+
>
239+
{{ metrics.fps > 0 ? `${metrics.fps} FPS` : 'N/A' }}
240+
</div>
241+
</div>
242+
243+
<!-- Memory Usage -->
244+
<div v-if="metrics.memoryUsage !== null" class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
245+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">
246+
Memory Usage
247+
</div>
248+
<div class="text-2xl font-bold text-pink-600 dark:text-pink-400">
249+
{{ metrics.memoryUsage }}MB
250+
</div>
251+
</div>
252+
</div>
253+
254+
<!-- Controls -->
255+
<div class="flex items-center gap-4 flex-wrap">
256+
<label class="flex items-center gap-2">
257+
<span class="text-sm text-gray-700 dark:text-gray-300">Tooltip Count:</span>
258+
<input
259+
v-model.number="tooltipCount"
260+
type="number"
261+
min="1"
262+
max="5000"
263+
step="100"
264+
class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
265+
>
266+
</label>
267+
268+
<Button
269+
class="!px-4 !py-2"
270+
label="Re-run Benchmark"
271+
:disabled="isRendering"
272+
@click="measurePerformance"
273+
/>
274+
275+
<span v-if="isRendering" class="text-sm text-gray-600 dark:text-gray-400 italic">
276+
Running benchmark...
277+
</span>
278+
</div>
279+
280+
<!-- Info -->
281+
<div class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded border border-blue-200 dark:border-blue-800">
282+
<p class="text-sm text-blue-800 dark:text-blue-300">
283+
💡 <strong>Tip:</strong> Hover over the buttons below to measure FPS in real-time.
284+
Lower element counts show better performance metrics.
285+
</p>
286+
</div>
287+
</div>
288+
289+
<!-- Performance test with tooltips -->
12290
<div class="flex flex-col gap-4">
13291
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
14-
Performance test
292+
Interactive Test Area
15293
</h3>
16-
<div class="flex flex-wrap gap-4 items-center">
294+
<div
295+
data-benchmark-container
296+
class="flex flex-wrap gap-2 items-center p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 max-h-96 overflow-y-auto"
297+
@mouseenter="handleMouseEnter"
298+
@mouseleave="handleMouseLeave"
299+
>
17300
<Button
18-
v-for="(label, idx) in Array.from({ length: 1000 }, (_, i) => `Button ${i + 1}`)" :key="idx"
301+
v-for="(label, idx) in buttons"
302+
:key="idx"
19303
v-tooltip="`Tooltip for ${label}`"
20304
:label="label"
21305
class="text-xs px-2 py-1"
306+
:data-benchmark-test="idx === 0 ? '' : undefined"
22307
/>
23308
</div>
24309
</div>

0 commit comments

Comments
 (0)