以前看到系统自带的画图工具, 感受挺有意思, 因而用react和canvas实现了个简易画图工具, 不用react也行, 我主要是由于在原来的项目里写的, 因此用了react。react
这里是线上地址: Paintgit
源码在项目下的Library中: 源码github
<canvas
id="canvas"
onMouseDown={mouseEvent}
onMouseMove={mouseEvent}
onMouseUp={mouseEvent}
>
</canvas>
复制代码
首先给canvas添加mousedown, mousemove, mouseup
三个监听事件。canvas
当鼠标按下的时候,将isDraw
开关打开,鼠标抬起时将其关闭, 这样就能控制mousemove
了, 否则一进入canvas的区域就会触发mousemove
事件。数组
这里为了简洁都监听的是mouseEvent事件, 在事件内部根据event.type
来触发不一样的逻辑。app
来捋一捋画笔的思路:工具
鼠标按下, 将isDraw开关打开;ui
拖住鼠标滑动触发mousemove
事件url
记录下鼠标点击的位置信息, 我这里用的是一个二维数组, 分别记录X轴和Y轴spa
初始化画笔的颜色和粗度以及形状
开始画线, 获取数组里鼠标画过的位置信息, 每两点之间进行一次描边, 这样点点链接就成了线
鼠标抬起, 将isDraw
关闭, 禁止触发mousemove
大致思路就是这样, 下面是代码实现:
const mouseEvent = (e) => {
let ctx = canvas2D.getContext('2d')
e.persist()
if (e.type === 'mousedown') {
switch (active) {
case 'spray':
return canvas2D.style.backgroundColor = color
default:
isDraw = true
arr = []
return
}
}
if (e.type === 'mousemove' && isDraw) {
arr.push([e.pageX - canvas2D.offsetLeft, e.pageY - document.querySelector('.admin-box').offsetTop - 40])
switch (active) {
case 'pen':
ctx.strokeStyle = color
ctx.lineJoin = "round";
ctx.lineWidth = 5;
ctx.beginPath();
arr.length > 1 && ctx.moveTo(arr[arr.length - 2][0], arr[arr.length - 2][1]);
ctx.lineTo(arr[arr.length - 1][0], arr[arr.length - 1][1]);
ctx.closePath();
ctx.stroke(); //描边
return
default:
return
}
}
if (e.type === 'mouseup') {
setCanvasUrl(url => {
url.push(canvas2D.toDataURL())
return url
})
isDraw = false
}
}
复制代码
橡皮擦这里比较取巧, 是用背景色覆盖掉了画笔的颜色,这样看起来就像擦除了同样。 思路和画笔的思路是同样的, 只是将颜色改为了背景色
case 'eraser':
ctx.strokeStyle = canvas2D.style.backgroundColor || '#ccc'
ctx.lineJoin = "round";
ctx.lineWidth = 50;
ctx.beginPath();
arr.length > 1 && ctx.moveTo(arr[arr.length - 2][0], arr[arr.length - 2][1]);
ctx.lineTo(arr[arr.length - 1][0], arr[arr.length - 1][1]);
ctx.closePath();
ctx.stroke(); //描边
return
复制代码
这个最简单, 获取到颜色板中的颜色, 将背景色替换掉就ok了
画矩形要用到clearRect
和strokeRect
两个方法, 它们接受4个参数, 分别是起点坐标X, Y和矩形的长, 宽。
思路:
每次触发mousemove
其实都会画矩形, 但咱们能够在每次画矩形前清空以前的区域。
clearRect
就是清空矩形内的痕迹
这样看起来就像是咱们画了一个矩形
case 'rectangle':
let left = arr[0][0]
let top = arr[0][1]
let prewidth = arr.length > 1 && arr[arr.length - 2][0] - left
let preheight = arr.length > 1 && arr[arr.length - 2][1] - top
let width = arr[arr.length - 1][0] - left
let height = arr[arr.length - 1][1] - top
ctx.beginPath();
ctx.lineWidth = "6";
ctx.strokeStyle = "red";
ctx.clearRect(left, top, prewidth, preheight)
ctx.strokeRect(left, top, width, height);
return
复制代码
这个要用到canvas的drawImage
和toDataURL
两个方法。
思路:
在每次操做以后, 将canvas经过toDataURL
方法转成img存起来,push到数组中
点击recall时, 经过drawImage
方法加载上次操做时的canvas图形。
移除数组最后的数据, 方便下次执行recall操做。
判断数组是否已清空, 清空了就再也不让操做了
代码以下:
const recallClick = (e) => {
let ctx = canvas2D.getContext('2d')
let step = canvasUrl.length - 1
if (step >= 0) {
step--;
ctx.clearRect(0, 0, 1000, 1000);
let canvasPic = new Image();
canvasPic.src = canvasUrl[step];
canvasPic.addEventListener('load', () => {
ctx.drawImage(canvasPic, 0, 0);
});
setCanvasUrl(canvasUrl => {
canvasUrl.pop()
return canvasUrl
})
} else {
console.log('不能再继续撤销了');
}
}
复制代码
经过toDataURL
获取当前canvas的图形。 建立一个a标签, 将地址赋给它, 执行点击事件下载。这个实现比较简陋, 但这是通常的思路, 也能够根据场景进行弹窗, 修改下载的相关信息。
代码以下:
const downloadImg = () => {
var url = canvas2D.toDataURL('image/png');
var a = document.createElement('a');
document.body.appendChild(a);
a.href = url;
a.download = '个人绘画';
a.target = '_blank';
a.click();
}
复制代码
这里使用的是react-color
插件。
到这里,简易画板的思路就交代清楚了, 后续各位还能够在这个基础上开发更多的功能。
fighting~