Files
labelimage/components/label/components/CrosshairComponent.tsx
2026-04-03 21:08:50 +08:00

184 lines
5.4 KiB
TypeScript

"use client"
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
} from "react"
import { useTopToolsStore } from "../useTopToolsStore"
interface CrosshairComponentProps {
isCarved: boolean
}
const CrosshairComponent = (
props: CrosshairComponentProps,
ref: React.Ref<unknown> | undefined
) => {
const { isCarved } = props
const { scale } = useTopToolsStore()
const canvasRef = useRef<HTMLCanvasElement>(null)
const lastPointerRef = useRef<{ x: number; y: number } | null>(null)
const getFixed = (sparsity: number) => {
const pointIdx = String(sparsity).indexOf(".")
const len = String(sparsity).length
return pointIdx < 0 ? 0 : len - pointIdx - 1
}
const isCloseToInteger = (num: number) => {
return Math.abs(num - Math.round(num)) < 0.0000001
}
const getSparsity = (scale: number) => {
if (scale <= 1) {
return Math.round(100 / scale)
} else if (scale <= 3) {
return 50
} else if (scale <= 4) {
return 20
} else if (scale <= 5) {
return 10
}
return 5
}
// 绘制十字线的函数
const drawCrosshair = useCallback(
(centerX: number, centerY: number) => {
const lineWidth = 2
const tickLength = 10
const canvas = canvasRef.current
const ctx = canvas?.getContext("2d")
if (ctx && canvas) {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 绘制水平和垂直线
ctx.beginPath()
// 水平
ctx.moveTo(centerX - canvas.width, centerY - lineWidth / 2)
ctx.lineTo(centerX + canvas.width, centerY - lineWidth / 2)
// 垂直
ctx.moveTo(centerX - lineWidth / 2, centerY - canvas.height)
ctx.lineTo(centerX - lineWidth / 2, centerY + canvas.height)
//
ctx.lineWidth = lineWidth
ctx.strokeStyle = "#EF4444"
ctx.setLineDash([5, 2])
ctx.stroke()
if (isCarved) {
ctx.beginPath()
ctx.setLineDash([])
ctx.strokeStyle = "#EF4444"
ctx.fillStyle = "#EF4444"
let carveLineWidth = 1
ctx.lineWidth = carveLineWidth
// 间隔
const sparsity = getSparsity(scale)
const part = 10
const pixelPerUnit = scale * sparsity
const gap = pixelPerUnit / part
const fixed = getFixed(sparsity)
// 横向刻度
let indexX =
centerX % gap > 0 ? gap - (centerX % gap) : -centerX % gap
do {
const num = ((centerX + indexX) / pixelPerUnit) * sparsity
if (isCloseToInteger(num / sparsity)) {
ctx.moveTo(indexX, centerY - tickLength * 0.7 - carveLineWidth)
ctx.lineTo(indexX, centerY + tickLength * 0.7)
const text = (
((indexX - centerX) / pixelPerUnit) *
sparsity
).toFixed(fixed)
const textWidth = ctx.measureText(text).width
ctx.fillStyle = "#EF4444"
ctx.fillText(text, indexX - textWidth / 2, centerY - tickLength)
} else {
ctx.moveTo(indexX, centerY - tickLength / 2 - carveLineWidth)
ctx.lineTo(indexX, centerY + tickLength / 2)
}
indexX += gap
} while (indexX < 2 * canvas.width)
// 纵向刻度
let indexY =
centerY % gap > 0 ? gap - (centerY % gap) : -centerY % gap
do {
const num = ((centerY + indexY) / pixelPerUnit) * sparsity
if (isCloseToInteger(num / sparsity)) {
ctx.moveTo(centerX - tickLength * 0.7 - carveLineWidth, indexY)
ctx.lineTo(centerX + tickLength * 0.7, indexY)
const text = (
((-indexY + centerY) / pixelPerUnit) *
sparsity
).toFixed(fixed)
const textWidth = ctx.measureText(text).width
ctx.fillStyle = "#EF4444"
ctx.fillText(text, centerX + tickLength, indexY - textWidth / 2)
} else {
ctx.moveTo(centerX - tickLength / 2 - carveLineWidth, indexY)
ctx.lineTo(centerX + tickLength / 2, indexY)
}
indexY += gap
} while (indexY < 2 * canvas.height)
ctx.stroke()
}
}
},
[isCarved, scale]
)
useEffect(() => {
const canvas = canvasRef.current
if (canvas) {
// 设置canvas尺寸
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const rect = canvas.getBoundingClientRect()
const center = lastPointerRef.current || {
x: rect.width / 2,
y: rect.height / 2,
}
drawCrosshair(center.x, center.y)
}
}, [drawCrosshair])
const updateCrosshair = (event: { clientX: number; clientY: number }) => {
const canvas = canvasRef.current
if (canvas) {
const rect = canvas.getBoundingClientRect()
const centerX = event.clientX - rect.left
const centerY = event.clientY - rect.top
lastPointerRef.current = { x: centerX, y: centerY }
drawCrosshair(centerX, centerY)
}
}
useImperativeHandle(ref, () => ({ updateCrosshair }))
return (
<canvas
ref={canvasRef}
style={{
backgroundColor: "transparent",
position: "absolute",
zIndex: 50,
pointerEvents: "none",
}}
/>
)
}
export default forwardRef(CrosshairComponent)