以前看到系統自帶的畫圖工具, 感受挺有意思, 因而用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~