953f598b9b
Componentes React reutilizables: card, dialog, tabs, select, alert, badge, button, input, label, skeleton, tooltip, progress_bar, page_header, form_field, settings_page, crud_page, analytics_page, dashboard_layout. Charts: area, bar, line, sparkline, kpi_card, chart_container. Hooks Wails: use_wails_query, use_wails_mutation, use_wails_stream, use_wails_event, use_animated_canvas. Funciones core: cn, format_compact, chart_colors, get_series_color, wails_cache, theme_config_to_colors. Tipos: chart_series, wails_ipc, theme_config, component_variants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
import { useEffect, useRef } from 'react'
|
|
|
|
export interface AnimatedCanvasOptions {
|
|
/** Target FPS (default 60). El throttle real es 1000/fps ms. */
|
|
fps?: number
|
|
/** Callback de dibujo. Recibe el canvas 2d context y dimensiones. */
|
|
draw: (ctx: CanvasRenderingContext2D, width: number, height: number, frameCount: number) => void
|
|
}
|
|
|
|
export interface AnimatedCanvasResult {
|
|
canvasRef: React.RefObject<HTMLCanvasElement | null>
|
|
/** FPS real de renderizado (actualizado cada segundo) */
|
|
renderFpsRef: React.RefObject<number>
|
|
}
|
|
|
|
/**
|
|
* Hook para renderizar un canvas a N fps usando requestAnimationFrame.
|
|
* Maneja DPR, resize, y throttling automático.
|
|
*/
|
|
export function useAnimatedCanvas(options: AnimatedCanvasOptions): AnimatedCanvasResult {
|
|
const { fps = 60, draw } = options
|
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
const renderFpsRef = useRef(0)
|
|
const rafRef = useRef(0)
|
|
const lastDrawRef = useRef(0)
|
|
const frameCountRef = useRef(0)
|
|
const fpsTimerRef = useRef(0)
|
|
const drawRef = useRef(draw)
|
|
|
|
// Keep draw ref updated without re-subscribing the effect
|
|
drawRef.current = draw
|
|
|
|
useEffect(() => {
|
|
const interval = 1000 / fps
|
|
|
|
const loop = (now: number) => {
|
|
rafRef.current = requestAnimationFrame(loop)
|
|
|
|
if (now - lastDrawRef.current < interval) return
|
|
lastDrawRef.current = now
|
|
|
|
// FPS counting
|
|
frameCountRef.current++
|
|
if (now - fpsTimerRef.current >= 1000) {
|
|
renderFpsRef.current = frameCountRef.current
|
|
frameCountRef.current = 0
|
|
fpsTimerRef.current = now
|
|
}
|
|
|
|
const canvas = canvasRef.current
|
|
if (!canvas) return
|
|
|
|
const rect = canvas.getBoundingClientRect()
|
|
const w = rect.width
|
|
const h = rect.height
|
|
const dpr = window.devicePixelRatio || 1
|
|
|
|
const targetW = Math.floor(w * dpr)
|
|
const targetH = Math.floor(h * dpr)
|
|
|
|
if (canvas.width !== targetW || canvas.height !== targetH) {
|
|
canvas.width = targetW
|
|
canvas.height = targetH
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d')
|
|
if (!ctx) return
|
|
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
ctx.clearRect(0, 0, w, h)
|
|
|
|
drawRef.current(ctx, w, h, frameCountRef.current)
|
|
}
|
|
|
|
rafRef.current = requestAnimationFrame(loop)
|
|
return () => cancelAnimationFrame(rafRef.current)
|
|
}, [fps])
|
|
|
|
return { canvasRef, renderFpsRef }
|
|
}
|