提供撤销,清除,导出功能 图片格式为 png, 可自行调整
注意: canvas 宽高要设置 width,height。 设置在style里面是没有用的,会导致画图的的设备的移动倍率不正确,导致鼠标移动,实际上画出来的线偏移。偏离左上角越远,偏移越。
import React, { useImperativeHandle } from 'react'
import { memo, useEffect, useState } from 'react'
type TCoordinate = {
x: number
y: number
}
export default memo(
React.forwardRef((props, ref) => {
devicePixelRatio = 1
const [id] = useState(`signature-${Math.random().toString(36).slice(-8)}`)
const [thisCanvas, setThisCanvas] = useState<HTMLCanvasElement>()
const [isDown, setIsDown] = useState(false)
const [ctx, setCtx] = useState<CanvasRenderingContext2D>()
const [ponits, setPonits] = useState<TCoordinate[][]>([[]] as TCoordinate[][])
const [currentDrawIndex, setCurrentDrawIndex] = useState(0)
const [lastPoint, setLastPoint] = useState<TCoordinate>({ x: 0, y: 0 })
const draw = (
ctx: CanvasRenderingContext2D,
coordinate: { startX: number; startY: number; endX: number; endY: number },
) => {
ctx.moveTo(coordinate.startX, coordinate.startY)
ctx.lineTo(coordinate.endX, coordinate.endY)
ctx.stroke()
!ponits[currentDrawIndex] && (ponits[currentDrawIndex] = [])
ponits[currentDrawIndex].push({ x: coordinate.endX, y: coordinate.endY })
}
const getCurrentCoordinate = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
if (thisCanvas) {
const canvasRect = thisCanvas.getBoundingClientRect()
const x = e.clientX - canvasRect.left
const y = e.clientY - canvasRect.top
// 记录)*起点 x,y
return { x, y }
} else {
return { x: 0, y: 0 }
}
}
const mouseLeave = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
if (isDown) {
setCurrentDrawIndex(currentDrawIndex + 1)
ctx?.closePath()
setIsDown(false)
setLastPoint({} as TCoordinate)
}
}
const mouseDown = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
setIsDown(true)
ctx?.beginPath()
}
const mouseMove = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
const coordinate = getCurrentCoordinate(e)
if (ctx && isDown) {
lastPoint.x &&
draw(ctx, {
endX: coordinate.x * devicePixelRatio,
endY: coordinate.y * devicePixelRatio,
startX: lastPoint.x * devicePixelRatio,
startY: lastPoint.y * devicePixelRatio,
})
!ponits[currentDrawIndex] && (ponits[currentDrawIndex] = [])
ponits[currentDrawIndex].push({ x: coordinate.x, y: coordinate.y })
setPonits([...ponits])
setLastPoint({ x: coordinate.x, y: coordinate.y })
}
}
const mouseUp = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
setIsDown(false)
setLastPoint({} as TCoordinate)
setCurrentDrawIndex(currentDrawIndex + 1)
ctx?.closePath()
}
const undo = () => {
if (ctx && thisCanvas) {
ctx.clearRect(0, 0, thisCanvas.width, thisCanvas.height)
}
if (ponits.length) {
currentDrawIndex !== 0 && setCurrentDrawIndex(currentDrawIndex - 1)
const resultArr = ponits.slice(0, ponits.length - 1)
resultArr.forEach((line) => {
if (line?.length > 1 && ctx) {
line.forEach((xy, index) => {
ctx?.beginPath()
if (line[index + 1]?.x) {
draw(ctx, {
startX: xy.x,
startY: xy.y,
endX: line[index + 1].x,
endY: line[index + 1].y,
})
}
if (index === line.length - 1) {
ctx?.closePath()
}
})
}
})
setLastPoint({} as TCoordinate)
setPonits([...resultArr])
}
}
const reset = () => {
if (ctx && thisCanvas) {
ctx.clearRect(0, 0, thisCanvas.width, thisCanvas.height)
}
setPonits([[]])
setCurrentDrawIndex(0)
setLastPoint({} as TCoordinate)
}
useEffect(() => {
const c = document.getElementById(id) as HTMLCanvasElement
c && setThisCanvas(c)
if (c.getContext) {
const ctx = c.getContext('2d') as CanvasRenderingContext2D
setCtx(ctx)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
}
}, [])
const toImage = () => {
if (thisCanvas) {
const image = thisCanvas.toDataURL('image/png')
return image
// let link = document.createElement('a')
// document.body.appendChild(link)
// link.download = 'picture.png'
// link.href = image
// // 触发点击
// link.click()
// // 移除元素
// document.body.removeChild(link)
}
}
useImperativeHandle(ref, () => ({
undo: undo,
reset: reset,
getImage: toImage,
}))
return (
<div>
<div onClick={undo}>撤销</div>
<div onClick={reset}>重写</div>
<div onClick={toImage}>图片</div>
<canvas
style={{ border: '1px solid' }}
id={id}
width={`${500 * devicePixelRatio} px`}
height={`${300 * devicePixelRatio} px`}
onMouseLeave={mouseLeave}
onMouseDown={mouseDown}
onMouseUp={mouseUp}
onMouseMove={mouseMove}
>
Your browser doesn't suppot this function, please use the last edition of browsers like:
Chorme / Egde / Firefox
</canvas>
</div>
)
}),
)
修改记录: devicePixelRatio 不要乘个这个倍率,用了反而会有偏移,默认1 的就好