去除/整理性能调试相关代码

This commit is contained in:
qiuchengw
2025-12-03 09:04:23 +08:00
parent 50a4df70f0
commit 6cd243e1fd
3 changed files with 230 additions and 189 deletions
+6 -32
View File
@@ -17,12 +17,6 @@ const props = withDefaults(defineProps<Props>(), {
const canvasRef = ref<HTMLCanvasElement | null>(null) const canvasRef = ref<HTMLCanvasElement | null>(null)
// 性能监控
const ENABLE_PERF_MONITOR = false // 生产环境关闭
let drawCount = 0
let drawTotalTime = 0
let lastReportTime = 0
// 缓存 canvas 上下文和尺寸信息,避免重复初始化 // 缓存 canvas 上下文和尺寸信息,避免重复初始化
let cachedCtx: CanvasRenderingContext2D | null = null let cachedCtx: CanvasRenderingContext2D | null = null
let cachedWidth = 0 let cachedWidth = 0
@@ -75,8 +69,6 @@ const initCanvas = () => {
* @param isValidTarget 是否是合法的连接目标 * @param isValidTarget 是否是合法的连接目标
* @param errorMessage 错误提示消息 * @param errorMessage 错误提示消息
*/ */
// 🔧 调试开关:跳过实际绘制
const DEBUG_SKIP_ACTUAL_DRAW = false
const USE_SIMPLE_LINE = true // true = 使用简单直线替代贝塞尔曲线(性能更好) const USE_SIMPLE_LINE = true // true = 使用简单直线替代贝塞尔曲线(性能更好)
const draw = ( const draw = (
@@ -87,14 +79,10 @@ const draw = (
isValidTarget = true, isValidTarget = true,
errorMessage = '', errorMessage = '',
) => { ) => {
// 🔧 调试:跳过实际绘制
if (DEBUG_SKIP_ACTUAL_DRAW) return
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
const ctx = initCanvas() const ctx = initCanvas()
if (!ctx) { if (!ctx) {
console.warn('[LinkDragGuide] initCanvas returned null') // eslint-disable-next-line no-console
console.error('[LinkDragGuide] initCanvas returned null')
return return
} }
@@ -132,8 +120,10 @@ const draw = (
const color = isValidTarget ? '#67c23a' : '#f56c6c' const color = isValidTarget ? '#67c23a' : '#f56c6c'
ctx.strokeStyle = color ctx.strokeStyle = color
ctx.lineWidth = 3 ctx.lineWidth = 3
// 🔧 测试:移除虚线,看是否是虚线+贝塞尔曲线导致的性能问题 // 虚线绘制也会导致巨大的性能开销,慎用
// ctx.setLineDash([8, 4]) if (!USE_SIMPLE_LINE) {
ctx.setLineDash([8, 4])
}
ctx.globalAlpha = 0.8 ctx.globalAlpha = 0.8
// 绘制直线 // 绘制直线
@@ -190,22 +180,6 @@ const draw = (
} }
ctx.restore() ctx.restore()
if (ENABLE_PERF_MONITOR) {
drawCount++
drawTotalTime += performance.now() - startTime
const now = Date.now()
if (now - lastReportTime > 1000) {
const avgTime = drawCount > 0 ? (drawTotalTime / drawCount).toFixed(3) : 0
// eslint-disable-next-line no-console
console.log(`[LinkDragGuide Perf] 绘制次数: ${drawCount}/秒, 平均耗时: ${avgTime}ms`)
drawCount = 0
drawTotalTime = 0
lastReportTime = now
}
}
} }
/** /**
+60 -157
View File
@@ -8,6 +8,7 @@ import LinkDragGuide from './LinkDragGuide.vue'
import { useI18n } from '../composables/useI18n' import { useI18n } from '../composables/useI18n'
import type { TaskBarConfig } from '../models/configs/TaskBarConfig' import type { TaskBarConfig } from '../models/configs/TaskBarConfig'
import { getPredecessorIds } from '../utils/predecessorUtils' import { getPredecessorIds } from '../utils/predecessorUtils'
import { perfMonitor } from '../utils/perfMonitor'
import type { Task } from '../models/classes/Task' import type { Task } from '../models/classes/Task'
import type { Milestone } from '../models/classes/Milestone' import type { Milestone } from '../models/classes/Milestone'
import type { TimelineConfig } from '../models/configs/TimelineConfig' import type { TimelineConfig } from '../models/configs/TimelineConfig'
@@ -648,9 +649,6 @@ const setHighlightTask = (taskId: number) => {
} }
// ==================== 连接线拖拽状态管理 ==================== // ==================== 连接线拖拽状态管理 ====================
// 🔧 调试开关:禁用传递给 TaskBar 的响应式 props 以测试 Vue 渲染性能影响
const DISABLE_REACTIVE_PROPS_TO_TASKBAR = false // 设为 false 以启用正常的响应式 propslink anchor 需要此功能)
const dragLinkMode = ref<'predecessor' | 'successor' | null>(null) // 当前拖拽模式 const dragLinkMode = ref<'predecessor' | 'successor' | null>(null) // 当前拖拽模式
const linkDragSourceTask = shallowRef<Task | null>(null) // 拖拽起始任务(使用 shallowRef 优化性能) const linkDragSourceTask = shallowRef<Task | null>(null) // 拖拽起始任务(使用 shallowRef 优化性能)
const linkDragTargetTask = shallowRef<Task | null>(null) // 当前悬停的目标任务(使用 shallowRef 优化性能) const linkDragTargetTask = shallowRef<Task | null>(null) // 当前悬停的目标任务(使用 shallowRef 优化性能)
@@ -662,71 +660,11 @@ const linkAutoScrollInterval = ref<number | null>(null) // 自动滚动定时器
let nonReactiveIsValidTarget = false let nonReactiveIsValidTarget = false
let nonReactiveErrorMessage = '' let nonReactiveErrorMessage = ''
// 🔧 调试用:用于传递给 TaskBar 的静态替代值(避免触发 Vue 更新)
const staticDragLinkMode = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : dragLinkMode.value,
)
const staticLinkDragSourceTaskId = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : linkDragSourceTask.value?.id,
)
const staticLinkDragTargetTaskId = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? null : linkDragTargetTask.value?.id,
)
const staticIsValidLinkTarget = computed(() =>
DISABLE_REACTIVE_PROPS_TO_TASKBAR ? false : isValidLinkTarget.value,
)
// 任务查找缓存 Map(优化性能,避免每次都遍历数组) // 任务查找缓存 Map(优化性能,避免每次都遍历数组)
const taskIdMap = new Map<number, Task>() const taskIdMap = new Map<number, Task>()
// 性能监控开关(开发调试用) // 性能监控开关(开发调试用)
const ENABLE_PERF_MONITOR = true const ENABLE_PERF_MONITOR = true
const perfStats = {
coordUpdateCount: 0,
coordUpdateTotalTime: 0,
targetDetectCount: 0,
targetDetectTotalTime: 0,
lastReportTime: 0,
}
// 帧时间监控(诊断主线程阻塞)
let frameMonitorId: number | null = null
let lastFrameTime = 0
let longFrameCount = 0
let frameCount = 0
const startFrameMonitor = () => {
lastFrameTime = performance.now()
longFrameCount = 0
frameCount = 0
const checkFrame = () => {
const now = performance.now()
const frameTime = now - lastFrameTime
frameCount++
// 超过 30ms 的帧被认为是"长帧"(正常应该是 ~16ms
if (frameTime > 30) {
longFrameCount++
// eslint-disable-next-line no-console
console.warn(`[Frame Monitor] 长帧检测: ${frameTime.toFixed(1)}ms`)
}
lastFrameTime = now
frameMonitorId = requestAnimationFrame(checkFrame)
}
frameMonitorId = requestAnimationFrame(checkFrame)
}
const stopFrameMonitor = () => {
if (frameMonitorId !== null) {
cancelAnimationFrame(frameMonitorId)
frameMonitorId = null
// eslint-disable-next-line no-console
console.log(`[Frame Monitor] 统计: ${frameCount}帧, 长帧数: ${longFrameCount} (${(longFrameCount / frameCount * 100).toFixed(1)}%)`)
}
}
// 开始连接线拖拽 // 开始连接线拖拽
const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'successor'; mouseEvent: MouseEvent }) => { const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'successor'; mouseEvent: MouseEvent }) => {
@@ -734,7 +672,7 @@ const handleLinkDragStart = (event: { task: Task; type: 'predecessor' | 'success
linkDragSourceTask.value = event.task linkDragSourceTask.value = event.task
// 启动帧监控 // 启动帧监控
startFrameMonitor() perfMonitor.startFrameMonitor()
// 初始化鼠标坐标(使用非响应式版本) // 初始化鼠标坐标(使用非响应式版本)
updateLinkDragCoordinatesNonReactive(event.mouseEvent.clientX, event.mouseEvent.clientY) updateLinkDragCoordinatesNonReactive(event.mouseEvent.clientX, event.mouseEvent.clientY)
@@ -781,11 +719,23 @@ const processLinkDragFrame = () => {
// 🔧 调试:逐步启用各操作 // 🔧 调试:逐步启用各操作
if (DEBUG_ENABLE_COORD_UPDATE) { if (DEBUG_ENABLE_COORD_UPDATE) {
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY) if (ENABLE_PERF_MONITOR) {
const startTime = performance.now()
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY)
perfMonitor.recordLinkDragCoordUpdate(performance.now() - startTime)
} else {
updateLinkDragCoordinatesNonReactive(pendingMouseX, pendingMouseY)
}
} }
if (DEBUG_ENABLE_TARGET_DETECT) { if (DEBUG_ENABLE_TARGET_DETECT) {
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY) if (ENABLE_PERF_MONITOR) {
const startTime = performance.now()
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY)
perfMonitor.recordLinkDragTargetDetect(performance.now() - startTime)
} else {
detectLinkTargetNonReactive(pendingMouseX, pendingMouseY)
}
} }
if (DEBUG_ENABLE_CANVAS_DRAW) { if (DEBUG_ENABLE_CANVAS_DRAW) {
@@ -824,7 +774,7 @@ const handleGlobalMouseUp = () => {
if (!dragLinkMode.value) return if (!dragLinkMode.value) return
// 停止帧监控 // 停止帧监控
stopFrameMonitor() perfMonitor.stopFrameMonitor()
// 🚀 取消待处理的 RAF // 🚀 取消待处理的 RAF
if (linkDragRafId !== null) { if (linkDragRafId !== null) {
@@ -859,22 +809,14 @@ let bodyRectCacheTime = 0
const BODY_RECT_CACHE_DURATION = 200 // 200ms 缓存(优化:增加缓存时间) const BODY_RECT_CACHE_DURATION = 200 // 200ms 缓存(优化:增加缓存时间)
let bodyRectInvalidated = false // 缓存失效标记(滚动时失效) let bodyRectInvalidated = false // 缓存失效标记(滚动时失效)
// 🚀 非响应式坐标更新(完全绕过 Vue 响应式系统 // 🚀 坐标更新辅助函数(提取重复逻辑
const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) => { const updateCoordinates = (mouseX: number, mouseY: number): void => {
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
if (!bodyContentRef.value) { if (!bodyContentRef.value) {
currentDragX = mouseX currentDragX = mouseX
currentDragY = mouseY currentDragY = mouseY
if (ENABLE_PERF_MONITOR) {
perfStats.coordUpdateCount++
perfStats.coordUpdateTotalTime += performance.now() - startTime
}
return return
} }
// 使用缓存的 rect 或获取新的
const now = Date.now() const now = Date.now()
const shouldRefreshRect = const shouldRefreshRect =
!cachedBodyRect || bodyRectInvalidated || now - bodyRectCacheTime > BODY_RECT_CACHE_DURATION !cachedBodyRect || bodyRectInvalidated || now - bodyRectCacheTime > BODY_RECT_CACHE_DURATION
@@ -885,36 +827,11 @@ const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) =>
} }
currentDragX = mouseX - cachedBodyRect.left currentDragX = mouseX - cachedBodyRect.left
currentDragY = mouseY - cachedBodyRect.top currentDragY = mouseY - cachedBodyRect.top
}
if (ENABLE_PERF_MONITOR) { // 🚀 非响应式坐标更新(完全绕过 Vue 响应式系统)
perfStats.coordUpdateCount++ const updateLinkDragCoordinatesNonReactive = (mouseX: number, mouseY: number) => {
perfStats.coordUpdateTotalTime += performance.now() - startTime updateCoordinates(mouseX, mouseY)
// 每秒输出一次性能统计
if (now - perfStats.lastReportTime > 1000) {
const avgCoordTime =
perfStats.coordUpdateCount > 0
? (perfStats.coordUpdateTotalTime / perfStats.coordUpdateCount).toFixed(3)
: 0
const avgTargetTime =
perfStats.targetDetectCount > 0
? (perfStats.targetDetectTotalTime / perfStats.targetDetectCount).toFixed(3)
: 0
// eslint-disable-next-line no-console
console.log(
`[LinkDrag Perf] 坐标更新: ${perfStats.coordUpdateCount}次, 平均${avgCoordTime}ms | ` +
`目标检测: ${perfStats.targetDetectCount}次, 平均${avgTargetTime}ms`,
)
// 重置统计
perfStats.coordUpdateCount = 0
perfStats.coordUpdateTotalTime = 0
perfStats.targetDetectCount = 0
perfStats.targetDetectTotalTime = 0
perfStats.lastReportTime = now
}
}
} }
// 拖拽过程中更新鼠标位置(🚀 优化:使用统一 RAF 调度) // 拖拽过程中更新鼠标位置(🚀 优化:使用统一 RAF 调度)
@@ -964,25 +881,17 @@ const handleLinkDragEnd = (event: { task: Task; type: 'predecessor' | 'successor
const ANCHOR_SIZE = 8 // 触点视觉大小(px const ANCHOR_SIZE = 8 // 触点视觉大小(px
const ANCHOR_TOLERANCE = 4 // 碰撞容差(px),扩大点击区域 const ANCHOR_TOLERANCE = 4 // 碰撞容差(px),扩大点击区域
// 🚀 非响应式目标检测(完全绕过 Vue 响应式系统 // 🚀 目标检测辅助函数(提取重复逻辑
// 缓存当前检测到的目标任务(用于 handleLinkDragEnd const detectTargetTaskId = (mouseX: number, mouseY: number): number | null => {
let nonReactiveTargetTask: Task | null = null
const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
const startTime = ENABLE_PERF_MONITOR ? performance.now() : 0
if (!linkDragSourceTask.value || !bodyContentRef.value) return
// 使用缓存的 rect // 使用缓存的 rect
if (!cachedBodyRect) { if (!cachedBodyRect) {
cachedBodyRect = bodyContentRef.value.getBoundingClientRect() cachedBodyRect = bodyContentRef.value!.getBoundingClientRect()
bodyRectCacheTime = Date.now() bodyRectCacheTime = Date.now()
} }
const relativeX = mouseX - cachedBodyRect.left const relativeX = mouseX - cachedBodyRect.left
const relativeY = mouseY - cachedBodyRect.top const relativeY = mouseY - cachedBodyRect.top
let foundTaskId: number | null = null
const isPredecessorMode = dragLinkMode.value === 'predecessor' const isPredecessorMode = dragLinkMode.value === 'predecessor'
const halfSize = (ANCHOR_SIZE + ANCHOR_TOLERANCE) / 2 const halfSize = (ANCHOR_SIZE + ANCHOR_TOLERANCE) / 2
const expandedHalfSize = halfSize + 10 const expandedHalfSize = halfSize + 10
@@ -998,40 +907,27 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
continue continue
} }
if (isPredecessorMode) { const anchorX = isPredecessorMode ? pos.left + pos.width : pos.left
const anchorX = pos.left + pos.width if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) { continue
continue }
}
const anchorY = pos.top + pos.height / 2 const anchorY = pos.top + pos.height / 2
if ( if (
relativeX >= anchorX - halfSize && relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize && relativeX <= anchorX + halfSize &&
relativeY >= anchorY - halfSize && relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize relativeY <= anchorY + halfSize
) { ) {
foundTaskId = taskId return taskId
break
}
} else {
const anchorX = pos.left
if (relativeX < anchorX - expandedHalfSize || relativeX > anchorX + expandedHalfSize) {
continue
}
const anchorY = pos.top + pos.height / 2
if (
relativeX >= anchorX - halfSize &&
relativeX <= anchorX + halfSize &&
relativeY >= anchorY - halfSize &&
relativeY <= anchorY + halfSize
) {
foundTaskId = taskId
break
}
} }
} }
const foundTarget = foundTaskId !== null ? taskIdMap.get(foundTaskId) || null : null return null
}
// 🚀 更新目标任务的状态
const updateTargetTaskState = (foundTarget: Task | null): void => {
const currentTargetId = nonReactiveTargetTask?.id ?? null const currentTargetId = nonReactiveTargetTask?.id ?? null
const newTargetId = foundTarget?.id ?? null const newTargetId = foundTarget?.id ?? null
@@ -1042,7 +938,6 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
const validation = validateLink(linkDragSourceTask.value, foundTarget, dragLinkMode.value!) const validation = validateLink(linkDragSourceTask.value, foundTarget, dragLinkMode.value!)
nonReactiveIsValidTarget = validation.valid nonReactiveIsValidTarget = validation.valid
nonReactiveErrorMessage = validation.error || '' nonReactiveErrorMessage = validation.error || ''
// 🚀 同步更新响应式变量(仅在目标变化时,用于 handleLinkDragEnd
isValidLinkTarget.value = validation.valid isValidLinkTarget.value = validation.valid
linkDragTargetTask.value = foundTarget linkDragTargetTask.value = foundTarget
} else { } else {
@@ -1052,11 +947,19 @@ const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
linkDragTargetTask.value = null linkDragTargetTask.value = null
} }
} }
}
if (ENABLE_PERF_MONITOR) { // 🚀 非响应式目标检测(完全绕过 Vue 响应式系统)
perfStats.targetDetectCount++ // 缓存当前检测到的目标任务(用于 handleLinkDragEnd
perfStats.targetDetectTotalTime += performance.now() - startTime let nonReactiveTargetTask: Task | null = null
}
const detectLinkTargetNonReactive = (mouseX: number, mouseY: number) => {
if (!linkDragSourceTask.value || !bodyContentRef.value) return
const foundTaskId = detectTargetTaskId(mouseX, mouseY)
const foundTarget = foundTaskId !== null ? taskIdMap.get(foundTaskId) || null : null
updateTargetTaskState(foundTarget)
} }
// 验证连接是否有效(返回 { valid: boolean, error?: string } // 验证连接是否有效(返回 { valid: boolean, error?: string }
@@ -4395,13 +4298,13 @@ const handleAddSuccessor = (task: Task) => {
:is-highlighted="highlightedTaskIds.has(task.id)" :is-highlighted="highlightedTaskIds.has(task.id)"
:is-primary-highlight="highlightedTaskId === task.id" :is-primary-highlight="highlightedTaskId === task.id"
:is-in-highlight-mode="isInHighlightMode" :is-in-highlight-mode="isInHighlightMode"
:drag-link-mode="staticDragLinkMode" :drag-link-mode="dragLinkMode"
:is-link-drag-source="staticLinkDragSourceTaskId === task.id" :is-link-drag-source="linkDragSourceTask?.id === task.id"
:is-valid-link-target=" :is-valid-link-target="
staticLinkDragTargetTaskId === task.id && staticIsValidLinkTarget === true linkDragTargetTask?.id === task.id && isValidLinkTarget === true
" "
:is-invalid-link-target=" :is-invalid-link-target="
staticLinkDragTargetTaskId === task.id && staticIsValidLinkTarget === false linkDragTargetTask?.id === task.id && isValidLinkTarget === false
" "
@update:task="updateTask" @update:task="updateTask"
@bar-mounted="handleBarMounted" @bar-mounted="handleBarMounted"
+164
View File
@@ -33,6 +33,38 @@ const drawStats: DrawStats = {
const MAX_RECENT_DRAWS = 100 // 保留最近 100 次绘制记录 const MAX_RECENT_DRAWS = 100 // 保留最近 100 次绘制记录
// ==================== 连接线拖拽性能监控 ====================
interface LinkDragStats {
coordUpdateCount: number
coordUpdateTotalTime: number
targetDetectCount: number
targetDetectTotalTime: number
lastReportTime: number
}
const linkDragStats: LinkDragStats = {
coordUpdateCount: 0,
coordUpdateTotalTime: 0,
targetDetectCount: 0,
targetDetectTotalTime: 0,
lastReportTime: 0,
}
// ==================== 帧时间监控 ====================
interface FrameMonitorStats {
frameMonitorId: number | null
lastFrameTime: number
longFrameCount: number
frameCount: number
}
const frameMonitorStats: FrameMonitorStats = {
frameMonitorId: null,
lastFrameTime: 0,
longFrameCount: 0,
frameCount: 0,
}
export const perfMonitor = { export const perfMonitor = {
/** /**
* 记录性能日志(自动节流) * 记录性能日志(自动节流)
@@ -260,4 +292,136 @@ export const perfMonitor = {
) )
}, durationMs) }, durationMs)
}, },
// ==================== 连接线拖拽性能监控 API ====================
/**
* 记录坐标更新操作
* @param duration 操作耗时(毫秒)
* @returns 是否应该输出性能报告(每秒输出一次)
*/
recordLinkDragCoordUpdate(duration: number): boolean {
linkDragStats.coordUpdateCount++
linkDragStats.coordUpdateTotalTime += duration
const now = Date.now()
if (now - linkDragStats.lastReportTime > 1000) {
const avgCoordTime =
linkDragStats.coordUpdateCount > 0
? (linkDragStats.coordUpdateTotalTime / linkDragStats.coordUpdateCount).toFixed(3)
: 0
const avgTargetTime =
linkDragStats.targetDetectCount > 0
? (linkDragStats.targetDetectTotalTime / linkDragStats.targetDetectCount).toFixed(3)
: 0
// eslint-disable-next-line no-console
console.log(
`[LinkDrag Perf] 坐标更新: ${linkDragStats.coordUpdateCount}次, 平均${avgCoordTime}ms | ` +
`目标检测: ${linkDragStats.targetDetectCount}次, 平均${avgTargetTime}ms`,
)
this.resetLinkDragStats()
linkDragStats.lastReportTime = now
return true
}
return false
},
/**
* 记录目标检测操作
* @param duration 操作耗时(毫秒)
*/
recordLinkDragTargetDetect(duration: number): void {
linkDragStats.targetDetectCount++
linkDragStats.targetDetectTotalTime += duration
},
/**
* 重置连接线拖拽性能统计
*/
resetLinkDragStats(): void {
linkDragStats.coordUpdateCount = 0
linkDragStats.coordUpdateTotalTime = 0
linkDragStats.targetDetectCount = 0
linkDragStats.targetDetectTotalTime = 0
},
/**
* 获取连接线拖拽性能统计数据
*/
getLinkDragStats() {
return {
coordUpdateCount: linkDragStats.coordUpdateCount,
coordUpdateTotalTime: linkDragStats.coordUpdateTotalTime,
targetDetectCount: linkDragStats.targetDetectCount,
targetDetectTotalTime: linkDragStats.targetDetectTotalTime,
}
},
// ==================== 帧时间监控 API ====================
/**
* 启动帧时间监控(诊断主线程阻塞)
* @param longFrameThreshold 长帧阈值(毫秒),默认 30ms
*/
startFrameMonitor(longFrameThreshold = 30): void {
frameMonitorStats.lastFrameTime = performance.now()
frameMonitorStats.longFrameCount = 0
frameMonitorStats.frameCount = 0
const checkFrame = () => {
const now = performance.now()
const frameTime = now - frameMonitorStats.lastFrameTime
frameMonitorStats.frameCount++
// 超过阈值的帧被认为是"长帧"
if (frameTime > longFrameThreshold) {
frameMonitorStats.longFrameCount++
// eslint-disable-next-line no-console
console.warn(`[Frame Monitor] 长帧检测: ${frameTime.toFixed(1)}ms`)
}
frameMonitorStats.lastFrameTime = now
frameMonitorStats.frameMonitorId = requestAnimationFrame(checkFrame)
}
frameMonitorStats.frameMonitorId = requestAnimationFrame(checkFrame)
},
/**
* 停止帧时间监控并输出统计信息
*/
stopFrameMonitor(): void {
if (frameMonitorStats.frameMonitorId !== null) {
cancelAnimationFrame(frameMonitorStats.frameMonitorId)
frameMonitorStats.frameMonitorId = null
const percentage =
frameMonitorStats.frameCount > 0
? (frameMonitorStats.longFrameCount / frameMonitorStats.frameCount * 100).toFixed(1)
: 0
// eslint-disable-next-line no-console
console.log(
`[Frame Monitor] 统计: ${frameMonitorStats.frameCount}帧, ` +
`长帧数: ${frameMonitorStats.longFrameCount} (${percentage}%)`,
)
}
},
/**
* 获取帧时间监控统计数据
*/
getFrameMonitorStats() {
return {
frameCount: frameMonitorStats.frameCount,
longFrameCount: frameMonitorStats.longFrameCount,
percentage:
frameMonitorStats.frameCount > 0
? (frameMonitorStats.longFrameCount / frameMonitorStats.frameCount * 100).toFixed(1)
: 0,
}
},
} }