1. 对应的react的js文件
import React, { useEffect, useRef, useState } from 'react'
import { Button } from 'antd'
import DefaultImage from 'layout/images/oranges.jpg'
export default function CanvasTag() {
const defaultSrc = DefaultImage
const [canvasWidth, setCanvasWidth] = useState(0)
const [canvasHeight, setCanvasHeight] = useState(0)
const [historyRect, setHistoryRect] = useState([])
const [isDrawing, setIsDrawing] = useState(false)
const [rectInfo, setRectInfo] = useState({})
const canvasWrapRef = useRef(null)
const nativeImgRef = useRef(null)
const canvasImgRef = useRef(null)
const canvasRectRef = useRef(null)
useEffect(() => {
initImgDraw()
}, [])
useEffect(() => {
// history 发生变化时重绘背景图层
const bgCtx = canvasRectRef.current?.getContext('2d')
if (bgCtx) {
// 擦除全部矩形
bgCtx.clearRect(
0,
0,
canvasRectRef.current?.width,
canvasRectRef.current?.height,
)
bgCtx.lineWidth = 2
bgCtx.strokeStyle = '#ff0000'
historyRect.forEach((rect) => {
bgCtx.strokeRect(
rect.startX,
rect.startY,
rect.endX - rect.startX,
rect.endY - rect.startY,
)
})
}
}, [historyRect])
useEffect(() => {
// 绘制信息发生改变时触发, 回执结束时执行, 并且不能是一个点
if (
!isDrawing &&
(rectInfo.startX !== rectInfo.endX || rectInfo.startY !== rectInfo.endY)
) {
setHistoryRect([...historyRect, rectInfo])
}
}, [rectInfo, isDrawing])
// canvas初始化
const initImgDraw = () => {
nativeImgRef.current.crossOrigin = 'anonymous'
const canvasContent = canvasImgRef.current.getContext('2d')
nativeImgRef.current.onload = () => {
const imgWidth = nativeImgRef.current.offsetWidth
const imgHeight = nativeImgRef.current.offsetHeight
const wrapWidth = canvasWrapRef.current.offsetWidth
const wrapHeight = (wrapWidth * imgHeight) / imgWidth
// 图片承载canvas绘制
canvasContent.drawImage(nativeImgRef.current, wrapWidth, wrapHeight)//绘制图片
setCanvasWidth(wrapWidth)
setCanvasHeight(wrapHeight)
// 矩形canvas长宽设置
canvasRectRef.current.width = wrapWidth
canvasRectRef.current.height = wrapHeight
}
}
/**
* 鼠标落下,开始绘制,记录起始坐标
* @param e
*/
const startDraw = (e) => {
setIsDrawing(true)
setRectInfo({
...rectInfo,
startX: e.nativeEvent.offsetX,
startY: e.nativeEvent.offsetY,
})
}
/**
* 鼠标抬起,结束绘制,记录结束坐标
* @param e
*/
const overDraw = (e) => {
setIsDrawing(false)
setRectInfo({
...rectInfo,
endX: e.nativeEvent.offsetX,
endY: e.nativeEvent.offsetY,
})
// 清除当前图层
const canvas = canvasRectRef.current
const ctx = canvas?.getContext('2d')
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
}
/**
* 鼠标移动
* @param e
*/
const drawRect = (e) => {
if (!isDrawing) {
return
}
const canvas = canvasRectRef.current
const ctx = canvas?.getContext('2d')
const { startX, startY } = rectInfo
if (ctx) {
ctx.lineWidth = 2
ctx.strokeStyle = '#ff0000'
ctx.clearRect(0, 0, canvas?.width, canvas?.height)
ctx.strokeRect(
startX,
startY,
(e.nativeEvent.offsetX - startX),
(e.nativeEvent.offsetY - startY),
)
}
}
/**
* 撤销上一步绘制
*/
const abortDraw = () => {
const history = [...historyRect]
history.splice(history.length - 1, 1)
setHistoryRect(history)
}
/**
* 保存绘制
*/
const saveDraw = () => {
//创建新的canvas
const newCanvas = document.createElement('canvas')
newCanvas.width = canvasWidth
newCanvas.height = canvasHeight
const newCanvasCtx = newCanvas.getContext('2d')
newCanvasCtx.drawImage(canvasImgRef.current, canvasWidth, canvasHeight)
newCanvasCtx.drawImage(canvasRectRef.current, canvasWidth, canvasHeight)
const exportPdfImg = { url: newCanvas.toDataURL('image/png') }
/*参数在传递形式写这篇文章的时候还没有确定,这里是只是一个 ajax 请求*/
}
return (
<div className="canvas-container">
<div className="canvasWrap" ref={canvasWrapRef}>
<img className="img-native" ref={nativeImgRef} src={defaultSrc} alt=""/>
<canvas id="canvasImg" ref={canvasImgRef}></canvas>
<canvas
id="canvasRect"
ref={canvasRectRef}
onMouseDown={startDraw}
onMouseUp={overDraw}
onMouseMove={drawRect}
>
</canvas>
</div>
<div className="operate-btn">
<Button type="primary" onClick={abortDraw}>撤销</Button>
<Button type="primary" onClick={saveDraw}>保存</Button>
</div>
</div>
)
}
2. 样式文件
.canvas-container{
width: 100%;
max-width: 1000px;
min-height: 500px;
.canvasWrap{
width: 100%;
min-height: 400px;
margin-bottom: 20px;
position: relative;
.img-native{
width: 100%;
height: auto;
}
#canvasImg{
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
}
#canvasRect{
position: absolute;
width: 100%;
height: 100%;
z-index: 2;
top:0;
left:0;
}
}
.operate-btn{
width: 100%;
padding: 20px 150px;
display: flex;
justify-content: space-evenly;
align-items: center;
}
}
效果如图

目前两个canvas合并成一张图片暂时没有成功,大家有好方法赶紧帮忙支招吧!!