在react项目中使用canvas绘图,在原有图片上绘制批阅痕迹,
支持撤回功能,保留前面的绘图痕迹,在绘制完毕后合成一张新的图片,
pic可以随便找一张图片
import React, { Component } from 'react';
import "./index.less";
let pic = '...';
class Canvas extends Component {
constructor(props) {
super(props);
this.state = {
imgObj: {},
isAllowDrawLine: false, //是否允许划线
finalImg: ''
}
}
componentDidMount() {
let that = this;
let canvasPic = new Image();
canvasPic.setAttribute("crossOrigin", 'Anonymous')
canvasPic.onload = function () {
let width = parseInt(canvasPic.width);
let height = parseInt(canvasPic.height);
let imgObj = {
width,
height,
url: pic,
step: -1,
historyCanvas: []
}
that.setState({
imgObj
})
}
canvasPic.src = pic
}
render() {
let { imgObj,finalImg } = this.state;
return (
<div className='canvas_page'
onMouseUp={(e) => { this.canvasMouseUp(e) }}>
<button onClick={this.canvasBack}>撤回</button>
<button onClick={this.hecheng}>合成</button>
{imgObj && imgObj.url
? <div className='canvas_box'>
<img src={imgObj.url} className='bg_img'
style={{ width: '990px', height: parseInt(990 * imgObj.height / imgObj.width) + 'px' }} alt="" />
<canvas id='myCanvas' width='990'
height={parseInt(990 * imgObj.height / imgObj.width)}
onMouseDown={(e) => { this.canvasMouseDown(e) }}
onMouseMove={(e) => { this.canvasMouseMove(e) }}
onMouseUp={(e) => { this.canvasMouseUp(e) }}
></canvas>
</div>
: null}
{finalImg
?<img src={finalImg} className='final_img'/>:null}
</div>
)
}
}
export default Canvas;
less文件如下
将作为背景的img底图设置为absolute,z-index:-1;
作为操作层的canvas的z-index:1,
.canvas_page {
position: relative;
width: 100%;
padding: 15px 2%;
box-sizing: border-box;
.final_img{
width: 300px;
}
>button {
height: 30px;
line-height: 28px;
padding: 0 20px;
letter-spacing: 2px;
color: #333;
border: 1px solid #333;
border-radius: 5px;
margin-right: 14px;
font-size: 12px;
margin-bottom: 10px;
background-color: #fff;
}
.canvas_box{
border: 1px solid #999;
padding: 1px;
position: relative;
>img{
z-index: -1;
position: absolute;
top: 0;
left: 0;
}
>canvas{
z-index: 1;
cursor:crosshair;
}
}
}
首先取一张底图,如上的pic
在 componentDidMount中设置图片对象,
用onload获取图片的宽和高,用来动态设置canvas的宽和高,做到和图片保持一致.
撤回实际上是将每次的轨迹保存起来,撤回的时候取上一次的轨迹重新渲染
historyCanvas 就是画笔的轨迹数组,step是步骤
以下是获取canvas内部鼠标的位置
//获取canvas内鼠标位置
getCanvasXY = (canvas, x, y) => {
let rect = canvas.getBoundingClientRect()
//x和y参数分别传入的是鼠标距离窗口的坐标,然后减去canvas距离窗口左边和顶部的距离。
return {
x: x - rect.left * (canvas.width / rect.width),
y: y - rect.top * (canvas.height / rect.height) - 1
}
}
在按下鼠标的时候设置画笔类型和起点,
canvasMouseDown = (e) => {
e.preventDefault();
let that = this;
let myCanvas = document.getElementById('myCanvas');
let context = myCanvas.getContext('2d');
let ele = that.getCanvasXY(myCanvas, e.clientX, e.clientY)
that.setState({
isAllowDrawLine: true
});
context.save();
context.setLineDash([5, 5]); //画虚线
context.strokeStyle = "rgb(230,11,9)";
context.moveTo(ele.x, ele.y);
}
鼠标移动的时候,判断是否鼠标被按下,如果被按下就画
由isAllowDrawLine判断 ,鼠标按下的时候设为true,松开的时候设为false
canvasMouseMove = (e) => {
let that = this;
let { isAllowDrawLine, imgObj } = that.state;
let myCanvas = document.getElementById('myCanvas');
let context = myCanvas.getContext('2d');
let ele = that.getCanvasXY(myCanvas, e.clientX, e.clientY);
if (isAllowDrawLine) {
context.lineTo(ele.x, ele.y)
context.stroke();
}
}
鼠标松开的时候,将你的画笔步数加1,并将画面保存到imgObj对象的historyCanvas数组中
canvasMouseUp = e => {
let that = this;
e.stopPropagation();
let { isAllowDrawLine, imgObj } = that.state;
let { step, historyCanvas } = imgObj;
let myCanvas = document.getElementById('myCanvas');
let context = myCanvas.getContext('2d');
if (!isAllowDrawLine) return
if (isAllowDrawLine) {
that.setState({
isAllowDrawLine: false
});
step++;
if (step < historyCanvas.length) {
historyCanvas.length = step; // 截断数组
}
historyCanvas.push(myCanvas.toDataURL()); // 添加新的绘制到历史记录
imgObj = { ...imgObj, step, historyCanvas }
console.log(imgObj)
that.setState({
imgObj
})
context.restore();
}
}
撤回的时候先判断是否有轨迹
如果有的话,先清空画布,即context.clearRect();
beginPath() 方法开始一条路径或重置当前的路径,一定不能忘了,
不然不能清除之前的轨迹
canvasBack = () => {
let that = this;
let { imgObj, isAllowDrawLine } = that.state;
let { step, historyCanvas } = imgObj;
let myCanvas = document.getElementById('myCanvas');
if (!myCanvas) return
let context = myCanvas.getContext('2d');
if (step >= 0) {
context.clearRect(0, 0, myCanvas.width, myCanvas.height);
context.beginPath();
if (step < historyCanvas.length) {
historyCanvas.length = step; // 截断数组
}
step--;
imgObj = { ...imgObj, step, historyCanvas }
if (historyCanvas.length > 0) {
let canvasPic = new Image();
canvasPic.onload = function () {
context.drawImage(canvasPic, 0, 0, myCanvas.width, myCanvas.height);
}
canvasPic.src = historyCanvas[step];
}
that.setState({
imgObj
})
} else {
alert('撤回到最后了')
}
}
在绘制完轨迹后将你的canvas画布和底图进行合并,制成一张新图,
首先拿到最后一个画布的轨迹 lastCanvas
新建一个和底图大小一致canvas,在底图onload之后将底图绘制到新建的canvas上,
然后再将我们的lastCanvas绘制到新的canvas上,实现两张图片的重叠合并,
拼成一张新的并展示
hecheng = () => {
let that = this;
let { imgObj } = that.state;
let { width, height, step, historyCanvas, url } = imgObj;
if (historyCanvas.length == 0) {
//没有经过绘图
that.setState({
finalImg: url
})
} else {
//合并canvas和图片
let lastCanvas = historyCanvas[historyCanvas.length - 1];
var Newcanvas = document.createElement('canvas');
var newContext = Newcanvas.getContext('2d');
Newcanvas.width = width;
Newcanvas.height = height;
let newImg = new Image();
newImg.setAttribute("crossOrigin", 'Anonymous')
newImg.onload = function () {
newContext.drawImage(newImg, 0, 0, width, height, 0, 0, Newcanvas.width, Newcanvas.height);
var canvasImg = new Image();
canvasImg.onload = function () {
newContext.drawImage(canvasImg, 0, 0, 990, parseInt(990 * height / width), 0, 0, Newcanvas.width, Newcanvas.height);
var dataURL = Newcanvas.toDataURL("image/png");
that.setState({
finalImg:dataURL
})
}
canvasImg.src = lastCanvas;
}
newImg.src = url;
}
}