184 lines
5.4 KiB
TypeScript
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)
|