JavaScript使用canvas實(shí)現(xiàn)手寫簽名功能
預(yù)覽效果

如果不想閱讀文章,可直接查看源碼
先實(shí)現(xiàn)基本需求(能簽名即可)
整理思路
準(zhǔn)備一個(gè)canvas畫布,得到context對(duì)象
指定畫筆的樣式
監(jiān)聽鼠標(biāo) / 手指的移動(dòng),得到每一次移動(dòng)在畫布上的坐標(biāo)點(diǎn),記錄下來
將這些點(diǎn)繪制到畫布上形成線條
<canvas?width="600"?height="400"?id="canvas"?style="background-color:?#ddd;"></canvas>
為了方便調(diào)試,我們本次僅演示pc端的操作。移動(dòng)端思路是一樣的,只不過監(jiān)聽的API不同。
常見的操作方式是:當(dāng)鼠標(biāo)左鍵按下的時(shí)候在畫布上移動(dòng)鼠標(biāo),就可以繪制。沒有按下鼠標(biāo)時(shí),不管它。
注釋很重要,我盡量寫得很詳細(xì)
window.onload?=?function?()?{????
//?默認(rèn)鼠標(biāo)是沒有按下的????
let?isDown?=?false;????
//?記錄上一次鼠標(biāo)的位置????
let?lastX?=?0;?//?x軸????
let?lastY?=?0;?//?y軸????????
//?獲取canvas
??const?canvas?=?document.getElementById("canvas");????
//?獲取canvas的上下文????
const?ctx?=?canvas.getContext("2d");????
//?定義線條的寬度,即畫筆的粗細(xì)????
ctx.lineWidth?=?3;????
//?定義畫筆的顏色????
ctx.strokeStyle?=?"#000";????
/**
??????* 定義繪制方法
????? *?線條其實(shí)是由兩個(gè)點(diǎn)連起來的一個(gè)線段
????? * 一個(gè)又一個(gè)的小線段,連起來就組成了一個(gè)線條
??????*?在畫布上繪制線條,主要用到的三個(gè)核心方法??????
* moveTo:?是 Canvas 2D API 將一個(gè)新的子路徑的起始點(diǎn)移動(dòng)到?(x,y)?坐標(biāo)的法。???
* lineTo:?是 Canvas 2D API 使用直線連接子路徑的終點(diǎn)到 x,y 坐標(biāo)的法。??????
*?當(dāng)然,定義了起點(diǎn)和終點(diǎn)還不夠,還需要手動(dòng)調(diào)用開始繪制這個(gè)路徑
? ? ? * startX 和 startY 一起組成了起點(diǎn)的坐標(biāo)
????? * endX?和 endY?一起組成了線段終點(diǎn)的坐標(biāo)
??????*/
????function?draw(startX, startY, endX, endY)?{????????
//?起點(diǎn)????????
ctx.moveTo(startX, startY);????????
//?終點(diǎn)????????
ctx.lineTo(endX, endY);????????
// 調(diào)用 stroke,即可看到繪制的線條????????
ctx.stroke();????
}????
//?監(jiān)聽鼠標(biāo)按下,得到按下時(shí)鼠標(biāo)在畫布上的坐標(biāo)????
canvas.addEventListener("mousedown",?({?x,?y?})?=>?{
isDown?=?true;
// 按下時(shí)的點(diǎn)作為起點(diǎn)
lastX?=?x;
lastY?=?y;
//?創(chuàng)建一個(gè)新的路徑
ctx.beginPath();
}, false);????
//?監(jiān)聽鼠標(biāo)移動(dòng)????
canvas.addEventListener("mousemove",?({?x,?y?})?=>?{????????????
//?沒有按下就不管
if?(!isDown)?return;
//?調(diào)用繪制方法
draw(lastX,?lastY,?x,?y);
//?把當(dāng)前移動(dòng)時(shí)的坐標(biāo)作為下一次的繪制路徑的起點(diǎn)
lastX?=?x;
lastY?=?y;
}, false);
//?監(jiān)聽鼠標(biāo)抬起
canvas.addEventListener("mouseup", ()?=>?{
isDown?=?false;
//?關(guān)閉路徑
ctx.closePath();
},?false);
// 監(jiān)聽鼠標(biāo)移出
canvas.addEventListener("mouseleave", () => {
// 移出canvas范圍,也認(rèn)為是鼠標(biāo)抬起了,再移入需要重新按下鼠標(biāo),避免移出之后再抬起鼠標(biāo),重新進(jìn)入畫筆還能繼續(xù)畫的問題
isDown = false;
ctx.closePath();
}, false);
};以上代碼就實(shí)現(xiàn)了最基本的簽名功能。
將canvas導(dǎo)出為圖片
這個(gè)功能比較簡(jiǎn)單,思路都在注釋里了
//?使用canvas的toDataURL()方法,將畫布內(nèi)容轉(zhuǎn)換為base64格式的圖片數(shù)據(jù):
let?imgData?=?canvas.toDataURL('image/png');?
//?創(chuàng)建下載鏈接
let?link?=?document.createElement('a');
link.download?=?'picture.png';
link.href?=?imgData;
//?觸發(fā)點(diǎn)擊
link.click();
//?移除元素
document.body.removeChild(link);撤銷和重寫功能
整理思路
要實(shí)現(xiàn)撤銷筆畫回到上一步,就要知道上一步畫了什么,就是要記錄下來,我們可以用一個(gè)數(shù)組,把每次鼠標(biāo)移動(dòng)時(shí)得到的坐標(biāo)放進(jìn)去。
通過基礎(chǔ)功能我們知道了,畫布上簽名,是由多個(gè)線條組成的,而線條是由很多個(gè)點(diǎn)組成的。那我們撤銷的時(shí)候,是撤銷一條線,即一個(gè)筆畫,而不是一個(gè)點(diǎn)。
那么,怎么知道哪些點(diǎn)是屬于一個(gè)筆畫的呢,就是要給這些點(diǎn)分組,一個(gè)筆畫為一組。我們規(guī)定,從鼠標(biāo)按下到鼠標(biāo)抬起,這之間移動(dòng)時(shí)產(chǎn)生的所有點(diǎn)為一組,即一個(gè)筆畫。用代碼表示,就是有多個(gè)數(shù)組,所以我們定一個(gè)二維數(shù)組來保存所有的點(diǎn)。
改寫一下前面的代碼
window.onload?=?function?()?{????
//?默認(rèn)鼠標(biāo)是沒有按下的????
let?isDown?=?false;????
//?//?記錄上一次鼠標(biāo)的位置????
//?let?lastX?=?0;?//?x軸????
//?let?lastY?=?0;?//?y軸??????
//?這次要用數(shù)組來記錄????
let?points?=?[];?//?這是一個(gè)筆畫的點(diǎn)????
let?allPonits?=?[];?//?這是所有筆畫的點(diǎn)????????
//?獲取canvas元素?????
const?canvas?=?document.getElementById("canvas");????//?獲取canvas的上下文????
const?ctx?=?canvas.getContext("2d");????//?定義線條的寬度,即畫筆的粗細(xì)????
ctx.lineWidth?=?3;????//?定義畫筆的顏色????
ctx.strokeStyle?=?"#000";????????
function?draw(startX, startY, endX, endY)?{????????
//?起點(diǎn)????????
ctx.moveTo(startX, startY);????????
//?終點(diǎn)????????
ctx.lineTo(endX, endY);????????
// 調(diào)用 stroke,即可看到繪制的線條????????
ctx.stroke();????
}????
//?監(jiān)聽鼠標(biāo)按下,得到按下時(shí)鼠標(biāo)在畫布上的坐標(biāo)????
canvas.addEventListener("mousedown", ({?x,?y?})?=>?{????????????
isDown?=?true;????????????
//?lastX?=?x;
//?lastY?=?y;
// 保存當(dāng)前坐標(biāo)作為起點(diǎn)
points.push({?x,?y?});????????????
//?創(chuàng)建一個(gè)新的路徑????????????
ctx.beginPath();????????
}, false);????
//?監(jiān)聽鼠標(biāo)移動(dòng)????
canvas.addEventListener("mousemove", ({?x,?y?})?=>?{
//?沒有按下就不管
if?(!isDown)?return;
//?調(diào)用繪制方法
//?draw(lastX,?lastY,?x,?y);
//?把當(dāng)前移動(dòng)時(shí)的坐標(biāo)作為下一次的繪制路徑的起點(diǎn)????????????
//?lastX?=?x;
//?lastY?=?y;
// 每次都取最后一個(gè)點(diǎn),作為繪制的起點(diǎn)
const?lastPoint?=?points.at(-1);
draw(lastPoint.x,?lastPoint.y,?x,?y);
//?把當(dāng)前的點(diǎn)保存起來,又作為下一次繪制的起點(diǎn)
points.push({?x,?y?});
}, false);
//?監(jiān)聽鼠標(biāo)抬起????
canvas.addEventListener("mouseup", (e)?=>?{????????????
isDown?=?false;
//?關(guān)閉路徑
ctx.closePath();
//?鼠標(biāo)抬起,說明當(dāng)前這一筆就結(jié)束了,把這一筆的所有點(diǎn)的數(shù)組放到總的里面
allPonits.push(points);
//?清空這一筆畫,為下一筆畫做準(zhǔn)備????????????
points?=?[];
}, false);
// 監(jiān)聽鼠標(biāo)移出
canvas.addEventListener("mouseleave", () => {
// 移出canvas范圍,也認(rèn)為是鼠標(biāo)抬起了,再移入需要重新按下鼠標(biāo),避免移出之后再抬起鼠標(biāo),重新進(jìn)入畫筆還能繼續(xù)畫的問題
isDown = false;
// 關(guān)閉畫筆
ctx.closePath();
// 如果是先抬起鼠標(biāo)再移出,那么points里面為空,就不保存了
// 如果是先移出范圍,移出時(shí)就保存,這樣也不會(huì)觸發(fā)上面的監(jiān)聽鼠標(biāo)抬起事件,也不會(huì)push。
if (points.length) {
allPonits.push(points);
}
// 移出時(shí)也清空,因?yàn)闊o法判斷是先抬起還是先移出的。
points = [];
}, false);
};在頁面上加兩個(gè)按鈕
<div> ??<button?id="prev">上一步</button> ??<button?id="reset">重寫</button> </div>
const?prev?=?document.getElementById("prev");
const?reset?=?document.getElementById("reset");
//?清空畫布
function?resetPath()?{
ctx.clearRect(0,?0,?canvas.width,?canvas.height);
}
//?上一步
prev.addEventListener("click",?(e)?=>?{
//?canvas本身不會(huì)記錄用戶的每一步操作
//?要回到上一步,只能一次性清空所有的
resetPath();
//?刪除最后一個(gè)筆畫
allPonits.pop();
//?遍歷所有的筆畫并重新繪制
// allPoints 是個(gè)二維數(shù)組
allPonits.forEach((ps)?=>?{
ps.forEach((item,?index)?=>?{
// 下一個(gè)坐標(biāo)點(diǎn)
let?next?=?ps[index?+?1];
if?(next)?{
// 有下一個(gè)點(diǎn)才執(zhí)行,否則到最后一個(gè)會(huì)報(bào)錯(cuò)
// 開始重新繪制
ctx.beginPath();
draw(item.x,?item.y,?next.x,?next.y);
ctx.closePath();
}
});??
});
});
//?重寫
reset.addEventListener("click", ()?=>?{
//?點(diǎn)擊重寫時(shí)清空畫布,并清空所有的點(diǎn)????
resetPath();
allPonits?=?[];
}, false);到這里,我們就完成了canvas手寫簽名,并且實(shí)現(xiàn)了撤銷和重寫,以及導(dǎo)出為圖片的功能。
以上就是JavaScript使用canvas實(shí)現(xiàn)手寫簽名功能的詳細(xì)內(nèi)容,更多關(guān)于JavaScript canvas手寫簽名的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解小程序中h5頁面onShow實(shí)現(xiàn)及跨頁面通信方案
這篇文章主要介紹了小程序中h5頁面onShow實(shí)現(xiàn)及跨頁面通信方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05
js實(shí)現(xiàn)人才網(wǎng)站職位選擇功能的方法
這篇文章主要介紹了js實(shí)現(xiàn)人才網(wǎng)站職位選擇功能的方法,涉及javascript動(dòng)態(tài)操作頁面元素結(jié)點(diǎn)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
js select下拉聯(lián)動(dòng) 更具級(jí)聯(lián)性!
這篇文章主要為大家詳細(xì)介紹了js select下拉聯(lián)動(dòng)的相關(guān)資料,更具級(jí)聯(lián)性!文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
靜態(tài)的動(dòng)態(tài)續(xù)篇之來點(diǎn)XML
靜態(tài)的動(dòng)態(tài)續(xù)篇之來點(diǎn)XML...2006-08-08

