本文主要實現了canvas塗鴉功能,包括塗鴉,前進後退、清除畫布以及畫筆大小修改。
實現思路
主要是監聽鼠標按下、移動、鬆開事件,將鼠標按下的值賦值給moveTo的x和y值,作爲起始位置。在移動事件中,將鼠標距離可視區x和y值賦給lineTo,再將路徑閉合即可。
canvas 塗鴉API及屬性
- ctx.beginPath(): 開始一條路徑,或重置當前的路徑
- ctx.moveTo(x,y): 把路徑移動到畫布中的指定點,不創建線條
- ctx.lineTo(x,y): 添加一個新點,然後創建從該點到畫布中最後指定點的線條(該方法並不會創建線條)
- ctx.stroke(): 實際地繪製出通過 moveTo() 和 lineTo() 方法定義的路徑。默認顏色是黑色
- ctx.fillStyle: 設置或返回用於填充繪畫的顏色、漸變或模式
- ctx.strokeStyle: 設置或返回用於筆觸的顏色、漸變或模式
- ctx.lineWidth: 設置或返回當前線條的寬度,以像素計
Canvas塗鴉主要用到的方法:moveTo、lineTo
實現過程
塗鴉方法
// 實現撤銷和重做的功能
let canvasHistory = [];
let step = -1;
let canX;//畫布左上角的x座標
let canY;//畫布左上角y座標
/*
* canvas 畫布標籤
* ctx 元素的context
* undo 撤銷元素id
* activeColor 畫筆顏色
*/
// 實現塗鴉功能
let drawing = function(canvas, ctx, undo, activeColor){
let _ = this;
canvas.onmousedown = function(ev){
let domObj = this.getBoundingClientRect();
canX = domObj.left;//獲取畫布左上角的x座標
canY = domObj.top;//獲取畫布左上角的y座標
// 鼠標在canvas元素區域中的座標之後對座標進行了縮放,從而獲得了鼠標相對於 canvas 元素繪圖表面的座標
// 處理放大縮小時,塗鴉畫不上去問題(踩了好長時間的坑~~~~~)
let x = (ev.clientX - canX) * (canvas.width / domObj.width);
let y = (ev.clientY - canY) * (canvas.height / domObj.height);
// 畫的時候動態修改畫布顏色(默認是黑色畫筆)
ctx.fillStyle = activeColor;
ctx.strokeStyle = activeColor;
ctx.beginPath();
ctx.moveTo(x,y);
canvas.onmousemove = function(ev){
let targetX = (ev.clientX - canX) * (canvas.width / domObj.width);
let targetY = (ev.clientY - canY) * (canvas.height / domObj.height);
ctx.lineWidth = _.penSize; // 畫筆大小
ctx.lineTo(targetX,targetY);
ctx.stroke();
};
canvas.onmouseup = function(){
canvas.onmousemove = null;
canvas.onmouseup = null;
canvasDraw();
};
// 繪製方法
function canvasDraw() {
step++;
if (step < canvasHistory.length) {
canvasHistory.length = step; // 截斷數組
}
// 添加新的繪製到歷史記錄
canvasHistory.push(canvas.toDataURL());
if (step > 0) {
undo.classList.add('active');
}
}
};
};
撤銷方法(上一步)
/*
* canvasHistory 存儲橡皮擦歷史記錄數組
* eraserStep 步長
* undo 撤銷元素id
* redo 再做元素id
* ctx 獲取這個元素的context——圖像稍後將在此被渲染
*/
let canvasUndo = async function(undo, redo, ctx, canvasId, imgSrc){
let _ = this;
if (eraserStep > 0) {
eraserStep--;
let canvasPic = new Image();
canvasPic.src = canvasHistory[eraserStep];
canvasPic.onload = function () {
ctx.drawImage(canvasPic, 0, 0);
};
undo.classList.add('active');
redo.classList.add('active');
} else if(eraserStep === 0){
eraserStep--;
canvasDrawImg(canvasId, imgSrc); // 回到畫布初始狀態
undo.classList.remove('active');
Message({
type: 'warning',
message: '不能再繼續撤銷了',
duration: 2000
});
}
};
重做方法(下一步)
let canvasRedo = async function(redo, ctx, canvas){
let _ = this;
if (step < canvasHistory.length - 1) {
step++;
let canvasPic = new Image();
canvasPic.src = canvasHistory[step];
canvasPic.onload = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(canvasPic, 0, 0);
};
} else {
redo.classList.remove('active');
Message({
type: 'warning',
message: '已經是最新的記錄了',
duration: 2000
});
}
};
撤銷方法
let resetEvent = async function(undo, redo, canvasId){
let _ = this;
canvasDrawImg(canvasId, _.matUrl);
canvasHistory = [];
undo.classList.remove('active');
redo.classList.remove('active');
};
canvas上繪製畫布
// canvas上繪製畫布
let canvasDrawImg = function(canvasId, imgSrc){
let canvas = document.getElementById(canvasId);
if (canvas) {
let ctx = canvas.getContext('2d');
// 畫布大小寬高619.5、400px
canvas.width = 619.5;
canvas.height = 495;
ctx.clearRect(0, 0, canvas.width, canvas.height); // 在給定矩形內清空一個矩形
let img = new Image();
img.src = imgSrc;
img.setAttribute("crossOrigin",'Anonymous');
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
}
}
};
踩過的坑
- 塗鴉時鼠標與畫筆位置出現偏移,位置無法吻合,出現這種問題的原因可能是頁面佈局導致,比如塗鴉容器設置了position:relative之類的,重點可以從這塊入手,儘量要用
- 塗鴉部分進行放大縮小之後,出現鼠標與畫筆位置偏移,位置無法吻合(排查了好久~~~),原因是塗鴉時鼠標點擊和鼠標移動時獲取的x、y有問題,需要改成如下形式:
之前寫的是下面這種:
- 如何保證在寬高固定的canvas上繪製之後圖片大小依舊保持不變,
即可以將最終得到的base64重新畫在另外一張畫布上,該畫布的寬高設置爲圖片的原始寬高即可,該canvas可以設置在頁面不可見部分
整體就這麼多啦,大家看時有什麼問題歡迎隨時溝通