var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), iptColor = document.getElementById('iptColor'),//画笔颜色 iptSize = document.getElementById('iptSize'),//画笔大小 btnClear = document.getElementById('btnClear'), //清除按钮 btnSave = document.getElementById('btnSave'),//保存按钮 canvasWidth = 800, canvasHeight = 600; canvas.setAttribute('width',canvasWidth); canvas.setAttribute('height',canvasHeight); iptSize.oninput = function() { document.querySelector('.txt-size').innerHTML = iptSize.value } canvas.addEventListener('mousedown',function(e){ var e = e || window.event; ctx.lineWidth = iptSize.value; ctx.strokeStyle = iptColor.value; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.beginPath(); ctx.moveTo(e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop); //线条起始位置 document.onmousemove = function(e) { var e = e || window.event; ctx.lineTo(e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop); ctx.stroke();//绘制线条 }; canvas.onmouseup = function() { document.onmousemove = null; document.onmouseup = null; }; }) //清除画布 btnClear.addEventListener('click',function(){ ctx.clearRect(0, 0, canvasWidth, canvasHeight); }) //保存图片到本地 btnSave.addEventListener('click',function(){ var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); var filename = '图片.png'; saveFile(imgData,filename) }) var saveFile = function(data, filename) { var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); save_link.href = data; save_link.download = filename; var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(event); };
这样看起来虽说好像没问题,可是这一句有一些问题:html
e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop
若是页面比较高,产生了滚动条,当滚动条向上滑的时候,绘图的位置就不许确了,示例如:https://codepen.io/jianxiujiu...。canvas
因此此处应该使用:工具
e.clientX - canvas.getBoundingClientRect().left,e.clientY - canvas.getBoundingClientRect().top
来替换。this
简单的绘制和图片保存完成了,可是在这种状况下,线条会有很明显的锯齿(灵魂画手来了)。spa
有一个简单粗暴的方法,将线条的边缘进行模糊:code
ctx.shadowBlur = 1; ctx.shadowColor = iptColor.value;
示例:https://codepen.io/jianxiujiu...
加上边缘模糊以后,线条明显柔和了许多,可是仍是有一个问题,就是在画笔收笔的地方,线条会变小(灵魂画手又来了)。htm
在通过搜索查阅以后,发现有一个绘制办法能够下降锯齿的问题。
原理如图:
绘制一个圆点,在下一个圆点之间,绘制一个矩形进行填充。这样绘制出来的线条的衔接会是圆滑的。图片
var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), btnClear = document.getElementById('btnClear'), btnSave = document.getElementById('btnSave'), iptColor = document.getElementById('iptColor'), iptSize = document.getElementById('iptSize'), canvasWidth = 800, canvasHeight = 600; canvas.setAttribute('width',canvasWidth); canvas.setAttribute('height',canvasHeight); iptSize.oninput = function() { document.querySelector('.txt-size').innerHTML = iptSize.value } canvas.addEventListener('mousedown',function(e){ var e = e || window.event; var x1 = e.clientX - canvas.getBoundingClientRect().left, y1 = e.clientY - canvas.getBoundingClientRect().top; lineSize = iptSize.value; lineColor = iptColor.value; ctx.beginPath(); ctx.fillStyle = lineColor; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.arc(x1, y1, lineSize, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); document.onmousemove = function(e) { var e = e || window.event; var x2 = e.clientX - canvas.getBoundingClientRect().left, y2 = e.clientY - canvas.getBoundingClientRect().top; var asin = lineSize * Math.sin(Math.atan((y2 - y1) / (x2 - x1))); var acos = lineSize * Math.cos(Math.atan((y2 - y1) / (x2 - x1))); //分别获取矩形的四个点的xy轴位置 var x3 = x1 + asin; var y3 = y1 - acos; var x4 = x1 - asin; var y4 = y1 + acos; var x5 = x2 + asin; var y5 = y2 - acos; var x6 = x2 - asin; var y6 = y2 + acos; ctx.beginPath(); ctx.fillStyle = lineColor; ctx.arc(x2, y2, lineSize, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = lineColor; ctx.moveTo(x3, y3); ctx.lineTo(x5, y5); ctx.lineTo(x6, y6); ctx.lineTo(x4, y4); ctx.fill(); ctx.closePath(); x1 = x2; y1 = y2; }; canvas.onmouseup = function() { document.onmousemove = null; document.onmouseup = null; }; }) btnClear.addEventListener('click',function(){ ctx.clearRect(0, 0, canvasWidth, canvasHeight); }) btnSave.addEventListener('click',function(){ var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); var filename = '图片.png'; saveFile(imgData,filename) }) var saveFile = function(data, filename) { var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); save_link.href = data; save_link.download = filename; var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(event); };
DEMO:
https://codepen.io/jianxiujiu...ip
用这个方法,咱们还能够加上橡皮擦。
橡皮擦的原理是,将橡皮绘制的路径覆盖到原来的画笔上。
这里咱们将用到canvas的一个属性globalCompositeOperation,详解参照:
http://www.w3school.com.cn/ta...rem
var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), btnClear = document.getElementById('btnClear'), btnSave = document.getElementById('btnSave'), btnRestore = document.getElementById('btnRestore'), iptColor = document.getElementById('iptColor'), iptSize = document.getElementById('iptSize'), eraser = document.getElementById('eraser') pen = document.getElementById('pen'), canvasWidth = 800, canvasHeight = 600, isClear = 0; canvas.setAttribute('width',canvasWidth); canvas.setAttribute('height',canvasHeight); iptSize.oninput = function() { document.querySelector('.txt-size').innerHTML = iptSize.value } eraser.addEventListener('click',function(){ isClear = 1; this.classList.add('cho'); pen.classList.remove('cho') }) pen.addEventListener('click',function(){ isClear = 0; this.classList.add('cho'); eraser.classList.remove('cho') }) canvas.addEventListener('mousedown',function(e){ var e = e || window.event; var x1 = e.clientX - canvas.getBoundingClientRect().left, y1 = e.clientY - canvas.getBoundingClientRect().top; lineSize = iptSize.value; if(isClear == 0){ lineColor = iptColor.value; ctx.beginPath(); ctx.fillStyle = lineColor; ctx.arc(x1, y1, lineSize, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); document.onmousemove = function(e) { draw(); }; btnClear.classList.remove('dis'); btnSave.classList.remove('dis'); }else{ ctx.strokeStyle = "rgba(250,250,250,0)"; document.onmousemove = function() { ctx.globalCompositeOperation = "destination-out"; draw(); ctx.globalCompositeOperation = "source-over" } } canvas.onmouseup = function() { document.onmousemove = null; document.onmouseup = null; }; draw = function(e){ var e = e || window.event, x2 = e.clientX - canvas.getBoundingClientRect().left, y2 = e.clientY - canvas.getBoundingClientRect().top, asin = lineSize * Math.sin(Math.atan((y2 - y1) / (x2 - x1))), acos = lineSize * Math.cos(Math.atan((y2 - y1) / (x2 - x1))), x3 = x1 + asin, y3 = y1 - acos, x4 = x1 - asin, y4 = y1 + acos, x5 = x2 + asin, y5 = y2 - acos, x6 = x2 - asin, y6 = y2 + acos; ctx.beginPath(); ctx.arc(x2, y2, lineSize, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = lineColor; ctx.moveTo(x3, y3); ctx.lineTo(x5, y5); ctx.lineTo(x6, y6); ctx.lineTo(x4, y4); ctx.fill(); ctx.closePath(); x1 = x2; y1 = y2; } }) // 清除 btnClear.addEventListener('click',function(){ ctx.clearRect(0, 0, canvasWidth, canvasHeight); this.classList.add('dis'); btnSave.classList.add('dis'); }) //保存图片 btnSave.addEventListener('click',function(){ var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); var filename = '图片.png'; saveFile(imgData,filename); this.classList.add('dis'); }) var saveFile = function(data, filename) { var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); save_link.href = data; save_link.download = filename; var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(event); };