优化:canvas绘制性能优化
This commit is contained in:
+161
-77
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick, onMounted, computed } from 'vue'
|
import { ref, watch, nextTick, onMounted, onUnmounted, computed } from 'vue'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
import { getPredecessorIds } from '../utils/predecessorUtils'
|
import { getPredecessorIds } from '../utils/predecessorUtils'
|
||||||
// import { perfMonitor } from '../utils/perfMonitor'
|
|
||||||
|
|
||||||
// 定义 TaskBar 位置信息类型
|
// 定义 TaskBar 位置信息类型
|
||||||
interface TaskBarPosition {
|
interface TaskBarPosition {
|
||||||
@@ -40,6 +39,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
// Canvas 引用
|
// Canvas 引用
|
||||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||||
|
|
||||||
|
// requestAnimationFrame 防抖控制
|
||||||
|
let rafId: number | null = null
|
||||||
|
let pendingRedraw = false
|
||||||
|
|
||||||
// 当前主题(用于分隔线颜色)
|
// 当前主题(用于分隔线颜色)
|
||||||
const isDarkTheme = computed(() => {
|
const isDarkTheme = computed(() => {
|
||||||
return document.documentElement.getAttribute('data-theme') === 'dark'
|
return document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
@@ -50,8 +53,6 @@ const isDarkTheme = computed(() => {
|
|||||||
* 性能优势:相比 SVG 提升 18 倍渲染性能
|
* 性能优势:相比 SVG 提升 18 倍渲染性能
|
||||||
*/
|
*/
|
||||||
const drawLinks = () => {
|
const drawLinks = () => {
|
||||||
// const startTime = performance.now()
|
|
||||||
|
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
|
|
||||||
@@ -101,7 +102,26 @@ const drawLinks = () => {
|
|||||||
// 是否处于高亮模式
|
// 是否处于高亮模式
|
||||||
const isHighlightMode = props.highlightedTaskId !== null
|
const isHighlightMode = props.highlightedTaskId !== null
|
||||||
|
|
||||||
// 绘制所有关系线
|
// 定义线条数据类型
|
||||||
|
interface LineData {
|
||||||
|
x1: number
|
||||||
|
y1: number
|
||||||
|
x2: number
|
||||||
|
y2: number
|
||||||
|
c1x: number
|
||||||
|
c1y: number
|
||||||
|
c2x: number
|
||||||
|
c2y: number
|
||||||
|
arrowAngle: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按样式分组线条数据(减少状态切换)
|
||||||
|
const highlightedLines: LineData[] = []
|
||||||
|
const hoveredLines: LineData[] = []
|
||||||
|
const normalLines: LineData[] = []
|
||||||
|
const fadedLines: LineData[] = [] // 高亮模式下的普通线条(半透明)
|
||||||
|
|
||||||
|
// 收集所有关系线数据并分组
|
||||||
for (const task of props.tasks) {
|
for (const task of props.tasks) {
|
||||||
if (!task.predecessor || !props.taskBarPositions[task.id]) continue
|
if (!task.predecessor || !props.taskBarPositions[task.id]) continue
|
||||||
|
|
||||||
@@ -140,80 +160,125 @@ const drawLinks = () => {
|
|||||||
const c2x = x2 - 40
|
const c2x = x2 - 40
|
||||||
const c2y = y2
|
const c2y = y2
|
||||||
|
|
||||||
// 设置线条样式
|
// 预计算箭头角度
|
||||||
ctx.beginPath()
|
const arrowAngle = Math.atan2(y2 - c2y, x2 - c2x)
|
||||||
|
|
||||||
|
const lineData: LineData = { x1, y1, x2, y2, c1x, c1y, c2x, c2y, arrowAngle }
|
||||||
|
|
||||||
|
// 根据状态分组
|
||||||
if (isLineHighlighted) {
|
if (isLineHighlighted) {
|
||||||
// 高亮状态:蓝色
|
highlightedLines.push(lineData)
|
||||||
ctx.strokeStyle = '#409eff'
|
|
||||||
ctx.lineWidth = 4
|
|
||||||
ctx.globalAlpha = 1
|
|
||||||
ctx.shadowBlur = 8
|
|
||||||
ctx.shadowColor = 'rgba(64, 158, 255, 0.4)'
|
|
||||||
} else if (isLineHovered) {
|
} else if (isLineHovered) {
|
||||||
// Hover 状态:绿色
|
hoveredLines.push(lineData)
|
||||||
ctx.strokeStyle = '#67c23a'
|
} else if (isHighlightMode) {
|
||||||
ctx.lineWidth = 3
|
fadedLines.push(lineData)
|
||||||
ctx.globalAlpha = 1
|
|
||||||
ctx.shadowBlur = 6
|
|
||||||
ctx.shadowColor = 'rgba(103, 194, 58, 0.3)'
|
|
||||||
} else {
|
} else {
|
||||||
// 普通状态:灰色
|
normalLines.push(lineData)
|
||||||
ctx.strokeStyle = '#c0c4cc'
|
|
||||||
ctx.lineWidth = 2
|
|
||||||
ctx.globalAlpha = isHighlightMode ? 0.2 : 1
|
|
||||||
ctx.shadowBlur = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.setLineDash([6, 4])
|
|
||||||
|
|
||||||
// 绘制贝塞尔曲线
|
|
||||||
ctx.moveTo(x1, y1)
|
|
||||||
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, x2, y2)
|
|
||||||
ctx.stroke()
|
|
||||||
|
|
||||||
// 重置阴影(避免影响箭头)
|
|
||||||
ctx.shadowBlur = 0
|
|
||||||
|
|
||||||
// 绘制箭头
|
|
||||||
drawArrow(ctx, x2, y2, c2x, c2y, isLineHighlighted, isLineHovered, isHighlightMode)
|
|
||||||
|
|
||||||
// 恢复全局透明度
|
|
||||||
ctx.globalAlpha = 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 性能监控
|
// 批量绘制:设置虚线样式(所有线条共用)
|
||||||
// const endTime = performance.now()
|
ctx.setLineDash([6, 4])
|
||||||
// const duration = endTime - startTime
|
|
||||||
// perfMonitor.log('Canvas 重绘', {
|
// 批量绘制高亮线条
|
||||||
// 耗时: `${duration.toFixed(2)}ms`,
|
if (highlightedLines.length > 0) {
|
||||||
// 关系线数量: props.tasks.filter(t => t.predecessor).length,
|
ctx.strokeStyle = '#409eff'
|
||||||
// 垂直线数量: props.verticalLines?.length || 0,
|
ctx.fillStyle = '#409eff'
|
||||||
// Canvas尺寸: `${rect.width}x${rect.height}`,
|
ctx.lineWidth = 4
|
||||||
// })
|
ctx.globalAlpha = 1
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
for (const line of highlightedLines) {
|
||||||
|
ctx.moveTo(line.x1, line.y1)
|
||||||
|
ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 批量绘制箭头
|
||||||
|
for (const line of highlightedLines) {
|
||||||
|
drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量绘制悬停线条
|
||||||
|
if (hoveredLines.length > 0) {
|
||||||
|
ctx.strokeStyle = '#67c23a'
|
||||||
|
ctx.fillStyle = '#67c23a'
|
||||||
|
ctx.lineWidth = 3
|
||||||
|
ctx.globalAlpha = 1
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
for (const line of hoveredLines) {
|
||||||
|
ctx.moveTo(line.x1, line.y1)
|
||||||
|
ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 批量绘制箭头
|
||||||
|
for (const line of hoveredLines) {
|
||||||
|
drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量绘制普通线条
|
||||||
|
if (normalLines.length > 0) {
|
||||||
|
ctx.strokeStyle = '#c0c4cc'
|
||||||
|
ctx.fillStyle = '#c0c4cc'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.globalAlpha = 1
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
for (const line of normalLines) {
|
||||||
|
ctx.moveTo(line.x1, line.y1)
|
||||||
|
ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 批量绘制箭头
|
||||||
|
for (const line of normalLines) {
|
||||||
|
drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量绘制半透明线条(高亮模式下的普通线条)
|
||||||
|
if (fadedLines.length > 0) {
|
||||||
|
ctx.strokeStyle = '#c0c4cc'
|
||||||
|
ctx.fillStyle = '#c0c4cc'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.globalAlpha = 0.2
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
for (const line of fadedLines) {
|
||||||
|
ctx.moveTo(line.x1, line.y1)
|
||||||
|
ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 批量绘制箭头
|
||||||
|
for (const line of fadedLines) {
|
||||||
|
drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复透明度
|
||||||
|
ctx.globalAlpha = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绘制箭头
|
* 优化版箭头绘制(减少参数传递,复用已设置的 fillStyle)
|
||||||
*/
|
*/
|
||||||
const drawArrow = (
|
const drawArrowOptimized = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
x2: number,
|
x2: number,
|
||||||
y2: number,
|
y2: number,
|
||||||
c2x: number,
|
angle: number,
|
||||||
c2y: number,
|
|
||||||
isHighlighted: boolean,
|
|
||||||
isHovered: boolean,
|
|
||||||
isHighlightMode: boolean,
|
|
||||||
) => {
|
) => {
|
||||||
const angle = Math.atan2(y2 - c2y, x2 - c2x)
|
|
||||||
const arrowLength = 8
|
const arrowLength = 8
|
||||||
const arrowWidth = 4
|
const arrowWidth = 4
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.fillStyle = isHighlighted ? '#409eff' : isHovered ? '#67c23a' : '#c0c4cc'
|
// fillStyle 已在外部设置,无需重复设置
|
||||||
ctx.globalAlpha = isHighlightMode && !isHighlighted ? 0.2 : 1
|
|
||||||
ctx.moveTo(x2, y2)
|
ctx.moveTo(x2, y2)
|
||||||
ctx.lineTo(
|
ctx.lineTo(
|
||||||
x2 - arrowLength * Math.cos(angle) - arrowWidth * Math.sin(angle),
|
x2 - arrowLength * Math.cos(angle) - arrowWidth * Math.sin(angle),
|
||||||
@@ -227,6 +292,31 @@ const drawArrow = (
|
|||||||
ctx.fill()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 requestAnimationFrame 优化的重绘调度器
|
||||||
|
* 合并多个连续的重绘请求为单次绘制
|
||||||
|
*/
|
||||||
|
const scheduleRedraw = () => {
|
||||||
|
if (pendingRedraw) {
|
||||||
|
// 已有待处理的重绘请求,跳过
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRedraw = true
|
||||||
|
|
||||||
|
// 取消之前的 RAF(如果有)
|
||||||
|
if (rafId !== null) {
|
||||||
|
cancelAnimationFrame(rafId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在下一帧绘制
|
||||||
|
rafId = requestAnimationFrame(() => {
|
||||||
|
pendingRedraw = false
|
||||||
|
rafId = null
|
||||||
|
drawLinks()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 监听相关状态变化,自动重绘 Canvas
|
// 监听相关状态变化,自动重绘 Canvas
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
@@ -240,24 +330,9 @@ watch(
|
|||||||
() => props.verticalLines,
|
() => props.verticalLines,
|
||||||
() => props.showVerticalLines,
|
() => props.showVerticalLines,
|
||||||
],
|
],
|
||||||
(newVals, oldVals) => {
|
() => {
|
||||||
// 记录是什么触发了重绘
|
// 使用 RAF 调度重绘,合并连续的多次变化为单次绘制
|
||||||
// const changes: string[] = []
|
scheduleRedraw()
|
||||||
// if (newVals[0] !== oldVals[0]) changes.push('taskBarPositions')
|
|
||||||
// if (newVals[1] !== oldVals[1]) changes.push('tasks.length')
|
|
||||||
// if (newVals[2] !== oldVals[2]) changes.push('highlightedTaskId')
|
|
||||||
// if (newVals[3] !== oldVals[3]) changes.push('highlightedTaskIds')
|
|
||||||
// if (newVals[4] !== oldVals[4]) changes.push('hoveredTaskId')
|
|
||||||
// if (newVals[5] !== oldVals[5]) changes.push('width')
|
|
||||||
// if (newVals[6] !== oldVals[6]) changes.push('height')
|
|
||||||
// if (newVals[7] !== oldVals[7]) changes.push('verticalLines')
|
|
||||||
// if (newVals[8] !== oldVals[8]) changes.push('showVerticalLines')
|
|
||||||
|
|
||||||
// perfMonitor.log('Canvas 触发重绘', { 变化属性: changes.join(', ') })
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
drawLinks()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{ deep: false }, // shallowRef 不需要 deep
|
{ deep: false }, // shallowRef 不需要 deep
|
||||||
)
|
)
|
||||||
@@ -269,6 +344,15 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 取消待处理的 RAF
|
||||||
|
if (rafId !== null) {
|
||||||
|
cancelAnimationFrame(rafId)
|
||||||
|
rafId = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 暴露方法供父组件调用
|
// 暴露方法供父组件调用
|
||||||
defineExpose({
|
defineExpose({
|
||||||
redraw: drawLinks,
|
redraw: drawLinks,
|
||||||
|
|||||||
@@ -5,6 +5,34 @@
|
|||||||
let lastLogTime = 0
|
let lastLogTime = 0
|
||||||
const LOG_THROTTLE = 100 // 最多每 100ms 输出一次日志
|
const LOG_THROTTLE = 100 // 最多每 100ms 输出一次日志
|
||||||
|
|
||||||
|
// FPS 监控相关
|
||||||
|
let fpsFrameCount = 0
|
||||||
|
let fpsLastTime = performance.now()
|
||||||
|
let fpsValues: number[] = []
|
||||||
|
let fpsMonitorEnabled = false
|
||||||
|
let fpsRafId: number | null = null
|
||||||
|
|
||||||
|
// 绘制性能统计
|
||||||
|
interface DrawStats {
|
||||||
|
totalDraws: number
|
||||||
|
totalDuration: number
|
||||||
|
minDuration: number
|
||||||
|
maxDuration: number
|
||||||
|
avgDuration: number
|
||||||
|
recentDraws: Array<{ timestamp: number; duration: number; details?: unknown }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawStats: DrawStats = {
|
||||||
|
totalDraws: 0,
|
||||||
|
totalDuration: 0,
|
||||||
|
minDuration: Infinity,
|
||||||
|
maxDuration: 0,
|
||||||
|
avgDuration: 0,
|
||||||
|
recentDraws: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_RECENT_DRAWS = 100 // 保留最近 100 次绘制记录
|
||||||
|
|
||||||
export const perfMonitor = {
|
export const perfMonitor = {
|
||||||
/**
|
/**
|
||||||
* 记录性能日志(自动节流)
|
* 记录性能日志(自动节流)
|
||||||
@@ -37,4 +65,199 @@ export const perfMonitor = {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动 FPS 监控
|
||||||
|
* @param intervalMs 刷新间隔(毫秒),默认 1000ms
|
||||||
|
*/
|
||||||
|
startFpsMonitor(intervalMs = 1000) {
|
||||||
|
if (fpsMonitorEnabled) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[Perf] FPS 监控已在运行中')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsMonitorEnabled = true
|
||||||
|
fpsFrameCount = 0
|
||||||
|
fpsLastTime = performance.now()
|
||||||
|
fpsValues = []
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[Perf] FPS 监控已启动')
|
||||||
|
|
||||||
|
const updateFps = () => {
|
||||||
|
if (!fpsMonitorEnabled) return
|
||||||
|
|
||||||
|
fpsFrameCount++
|
||||||
|
const currentTime = performance.now()
|
||||||
|
const elapsed = currentTime - fpsLastTime
|
||||||
|
|
||||||
|
if (elapsed >= intervalMs) {
|
||||||
|
const fps = Math.round((fpsFrameCount * 1000) / elapsed)
|
||||||
|
fpsValues.push(fps)
|
||||||
|
|
||||||
|
// 保留最近 60 个 FPS 值用于计算平均值
|
||||||
|
if (fpsValues.length > 60) {
|
||||||
|
fpsValues.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
const avgFps = Math.round(fpsValues.reduce((a, b) => a + b, 0) / fpsValues.length)
|
||||||
|
const minFps = Math.min(...fpsValues)
|
||||||
|
const maxFps = Math.max(...fpsValues)
|
||||||
|
|
||||||
|
// 根据 FPS 设置不同颜色
|
||||||
|
const color = fps >= 55 ? '#67c23a' : fps >= 30 ? '#e6a23c' : '#f56c6c'
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`%c[Perf] FPS: ${fps} | 平均: ${avgFps} | 最小: ${minFps} | 最大: ${maxFps}`,
|
||||||
|
`color: ${color}; font-weight: bold;`,
|
||||||
|
)
|
||||||
|
|
||||||
|
fpsFrameCount = 0
|
||||||
|
fpsLastTime = currentTime
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsRafId = requestAnimationFrame(updateFps)
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsRafId = requestAnimationFrame(updateFps)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止 FPS 监控
|
||||||
|
*/
|
||||||
|
stopFpsMonitor() {
|
||||||
|
if (!fpsMonitorEnabled) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[Perf] FPS 监控未运行')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsMonitorEnabled = false
|
||||||
|
if (fpsRafId !== null) {
|
||||||
|
cancelAnimationFrame(fpsRafId)
|
||||||
|
fpsRafId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出最终统计
|
||||||
|
if (fpsValues.length > 0) {
|
||||||
|
const avgFps = Math.round(fpsValues.reduce((a, b) => a + b, 0) / fpsValues.length)
|
||||||
|
const minFps = Math.min(...fpsValues)
|
||||||
|
const maxFps = Math.max(...fpsValues)
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`%c[Perf] FPS 监控已停止 | 总采样: ${fpsValues.length} | 平均: ${avgFps} | 最小: ${minFps} | 最大: ${maxFps}`,
|
||||||
|
'color: #909399; font-weight: bold;',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsValues = []
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录绘制性能
|
||||||
|
* @param duration 绘制耗时(毫秒)
|
||||||
|
* @param details 额外的详细信息
|
||||||
|
*/
|
||||||
|
recordDraw(duration: number, details?: unknown) {
|
||||||
|
drawStats.totalDraws++
|
||||||
|
drawStats.totalDuration += duration
|
||||||
|
drawStats.minDuration = Math.min(drawStats.minDuration, duration)
|
||||||
|
drawStats.maxDuration = Math.max(drawStats.maxDuration, duration)
|
||||||
|
drawStats.avgDuration = drawStats.totalDuration / drawStats.totalDraws
|
||||||
|
|
||||||
|
// 记录最近的绘制
|
||||||
|
drawStats.recentDraws.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
duration,
|
||||||
|
details,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 保持数组大小在限制内
|
||||||
|
if (drawStats.recentDraws.length > MAX_RECENT_DRAWS) {
|
||||||
|
drawStats.recentDraws.shift()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取绘制统计信息
|
||||||
|
*/
|
||||||
|
getDrawStats() {
|
||||||
|
return {
|
||||||
|
...drawStats,
|
||||||
|
recentDraws: drawStats.recentDraws.slice(-10), // 只返回最近 10 次
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印绘制统计信息
|
||||||
|
*/
|
||||||
|
printDrawStats() {
|
||||||
|
if (drawStats.totalDraws === 0) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[Perf] 暂无绘制数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const recent10 = drawStats.recentDraws.slice(-10)
|
||||||
|
const recent10Avg =
|
||||||
|
recent10.reduce((sum, item) => sum + item.duration, 0) / recent10.length
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
'%c[Perf] Canvas 绘制统计',
|
||||||
|
'color: #409eff; font-weight: bold; font-size: 14px;',
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.table({
|
||||||
|
总绘制次数: drawStats.totalDraws,
|
||||||
|
平均耗时: `${drawStats.avgDuration.toFixed(2)}ms`,
|
||||||
|
最小耗时: `${drawStats.minDuration.toFixed(2)}ms`,
|
||||||
|
最大耗时: `${drawStats.maxDuration.toFixed(2)}ms`,
|
||||||
|
最近10次平均: `${recent10Avg.toFixed(2)}ms`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置绘制统计
|
||||||
|
*/
|
||||||
|
resetDrawStats() {
|
||||||
|
drawStats.totalDraws = 0
|
||||||
|
drawStats.totalDuration = 0
|
||||||
|
drawStats.minDuration = Infinity
|
||||||
|
drawStats.maxDuration = 0
|
||||||
|
drawStats.avgDuration = 0
|
||||||
|
drawStats.recentDraws = []
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[Perf] 绘制统计已重置')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始完整的性能测试(FPS + 绘制统计)
|
||||||
|
* @param durationMs 测试持续时间(毫秒),默认 10000ms (10秒)
|
||||||
|
*/
|
||||||
|
startPerformanceTest(durationMs = 10000) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`%c[Perf] 性能测试开始(持续 ${durationMs / 1000} 秒)`,
|
||||||
|
'color: #409eff; font-weight: bold; font-size: 16px;',
|
||||||
|
)
|
||||||
|
|
||||||
|
this.resetDrawStats()
|
||||||
|
this.startFpsMonitor()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.stopFpsMonitor()
|
||||||
|
this.printDrawStats()
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
'%c[Perf] 性能测试完成',
|
||||||
|
'color: #67c23a; font-weight: bold; font-size: 16px;',
|
||||||
|
)
|
||||||
|
}, durationMs)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user