canvas小畫板——(2)熒光筆效果
我們在上一篇文章中講了如何繪製平滑曲線 canvas小畫板——(1)平滑曲線。
透明度實現熒光筆
現在我們需要加另外一種畫筆效果,帶透明度的熒光筆。那可能會覺得繪製畫筆的時候加上透明度就可以了。我們來在原來程式碼上設定
ctx.globalAlpha屬性為0.3,或者將strokeStyle設定為rgba的形式如rgba(55,55,55,0.3),程式碼如下:<!doctype html> <html> <head> <meta charset=utf-8> <style> canvas { border: 1px solid #ccc } body { margin: 0; } </style> </head> <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;"> <canvas id="c" width="1920" height="1080"></canvas> <script> var el = document.getElementById('c'); var ctx = el.getContext('2d'); //設定繪製線條樣式 ctx.globalAlpha=0.3; ctx.strokeStyle = 'red'; ctx.lineWidth = 10; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; var isDrawing;//標記是否要繪製 //儲存座標點 let points = []; document.body.onpointerdown = function (e) { console.log('pointerdown'); isDrawing = true; points.push({ x: e.clientX, y: e.clientY }); }; document.body.onpointermove = function (e) { console.log('pointermove'); if (isDrawing) { draw(e.clientX, e.clientY); } }; document.body.onpointerup = function (e) { if (isDrawing) { draw(e.clientX, e.clientY); } points = []; isDrawing = false; }; function draw(mousex, mousey) { points.push({ x: mousex, y: mousey }); ctx.beginPath(); let x = (points[points.length - 2].x + points[points.length - 1].x) / 2, y = (points[points.length - 2].y + points[points.length - 1].y) / 2; if (points.length == 2) { ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y); ctx.lineTo(x, y); } else { let lastX = (points[points.length - 3].x + points[points.length - 2].x) / 2, lastY = (points[points.length - 3].y + points[points.length - 2].y) / 2; ctx.moveTo(lastX, lastY); ctx.quadraticCurveTo(points[points.length - 2].x, points[points.length - 2].y, x, y); } ctx.stroke(); points.slice(0, 1); } </script> </body> </html>
我們滑鼠畫線出來的效果如下,可以看到有很多重疊區域:
對canvas有所瞭解的同學,知道
lineJoin和 lineCap的話可能會嘗試改變這兩個屬性,實現之後也有同樣重疊的效果。
解決熒光筆重疊問題
為什麼會有這種重疊渲染顏色的問題呢?細細品味程式碼,你會發現是因為每次move的時候繪製的部分是上個滑鼠點和當前滑鼠點之前的連線,這樣就會導致頭部和尾部有重疊部分多次被stroke了。(不同連線設定的頭部尾部重疊不同)
為了避免出現上述重疊這種問題下面介紹兩種方法。
利用globalCompositeOperation
現在我們需要用上另外一個api方法
globalCompositeOperation,具體介紹可以看我另外一篇博文講的比較詳細(Canvas學習:globalCompositeOperation詳解)。這個小畫板熒光筆效果我們需要使用globalCompositeOperation=‘xor’,另外注意透明度的設定不要使用context.globalAlpha,在設定strokeStyle的時候用rgba設定透明度顏色。這個設定也是我不斷嘗試得出來的,具體為什麼可以我也無法給出說法,有待研究或者知道的博友可以在評論給出答案。
1 <!doctype html> 2 <html> 3 4 <head> 5 <meta charset=utf-8> 6 <style> 7 canvas { 8 border: 1px solid #ccc 9 } 10 11 body { 12 margin: 0; 13 } 14 </style> 15 </head> 16 17 <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;"> 18 <canvas id="c" width="1920" height="1080"></canvas> 19 <script> 20 var el = document.getElementById('c'); 21 var ctx = el.getContext('2d'); 22 //設定繪製線條樣式 23 ctx.strokeStyle = 'rgba(253, 58, 43, 0.5)'; 24 ctx.lineWidth = 10; 25 ctx.lineJoin = 'round'; 26 ctx.lineCap = 'round'; 27 28 var isDrawing;//標記是否要繪製 29 //儲存座標點 30 let points = []; 31 document.body.onpointerdown = function (e) { 32 console.log('pointerdown'); 33 isDrawing = true; 34 points.push({ x: e.clientX, y: e.clientY }); 35 }; 36 document.body.onpointermove = function (e) { 37 console.log('pointermove'); 38 if (isDrawing) { 39 draw(e.clientX, e.clientY); 40 } 41 42 }; 43 document.body.onpointerup = function (e) { 44 if (isDrawing) { 45 draw(e.clientX, e.clientY); 46 } 47 points = []; 48 isDrawing = false; 49 }; 50 51 function draw(mousex, mousey) { 52 points.push({ x: mousex, y: mousey }); 53 ctx.globalCompositeOperation = "xor";//使用異或操作對源影象與目標影象進行組合。 54 ctx.beginPath(); 55 let x = (points[points.length - 2].x + points[points.length - 1].x) / 2, 56 y = (points[points.length - 2].y + points[points.length - 1].y) / 2; 57 if (points.length == 2) { 58 ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y); 59 ctx.lineTo(x, y); 60 } else { 61 let lastX = (points[points.length - 3].x + points[points.length - 2].x) / 2, 62 lastY = (points[points.length - 3].y + points[points.length - 2].y) / 2; 63 ctx.moveTo(lastX, lastY); 64 ctx.quadraticCurveTo(points[points.length - 2].x, points[points.length - 2].y, x, y); 65 } 66 ctx.stroke(); 67 points.slice(0, 1); 68 69 } 70 </script> 71 </body> 72 73 </html>
儲存座標點
另有一種普遍做法是使用陣列points儲存每個點的座標值,每次繪製前先清除畫布內容,再迴圈points陣列繪製路徑,最後進行一次stroke。
這種方法每次只能保留一條線條,因為在不斷的清除畫布內容,如果需要保留住的話,可以擴充套件下points為二維陣列,保留每一條線條的所有滑鼠點。清除畫布後遍歷points陣列重繪所有線條。
1 <!doctype html> 2 <html> 3 4 <head> 5 <meta charset=utf-8> 6 <style> 7 canvas { 8 border: 1px solid #ccc 9 } 10 11 body { 12 margin: 0; 13 } 14 </style> 15 </head> 16 17 <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;"> 18 <canvas id="c" width="1920" height="1080"></canvas> 19 <script> 20 var el = document.getElementById('c'); 21 var ctx = el.getContext('2d'); 22 //設定繪製線條樣式 23 ctx.globalAlpha = 0.3; 24 ctx.strokeStyle = 'red'; 25 ctx.lineWidth = 10; 26 ctx.lineJoin = 'round'; 27 ctx.lineCap = 'round'; 28 var isDrawing;//標記是否要繪製 29 //儲存座標點 30 let points = []; 31 document.body.onpointerdown = function (e) { 32 console.log('pointerdown'); 33 isDrawing = true; 34 points.push({ x: e.clientX, y: e.clientY }); 35 }; 36 document.body.onpointermove = function (e) { 37 console.log('pointermove'); 38 if (isDrawing) { 39 points.push({ x: e.clientX, y: e.clientY }); 40 draw(e.clientX, e.clientY); 41 } 42 43 }; 44 document.body.onpointerup = function (e) { 45 if (isDrawing) { 46 points.push({ x: e.clientX, y: e.clientY }); 47 draw(e.clientX, e.clientY); 48 } 49 points = []; 50 isDrawing = false; 51 }; 52 53 function draw(mousex, mousey) { 54 ctx.clearRect(0, 0, 1920, 1080); 55 ctx.beginPath(); 56 for (let i = 0; i < points.length; i++) { 57 if (i == 0) 58 ctx.moveTo(points[i].x, points[i].y); 59 else { 60 let p0 = points[i]; 61 let p1 = points[i + 1]; 62 let c, d; 63 if (!p1) { 64 c = p0.x; 65 d = p0.y; 66 }else { 67 c = (p0.x + p1.x) / 2; 68 d = (p0.y + p1.y) / 2; 69 } 70 ctx.quadraticCurveTo(p0.x, p0.y, c, d); //二次貝塞曲線函式 71 } 72 } 73 ctx.stroke(); 74 } 75 </script> 76 </body> 77 78 </html>
兩種解決方法對比
這兩種方法都可以實現熒光筆的效果,如下截圖:
第一種方法只繪製上個點和當前點,而第二種需要繪製所有線條,所以從流暢性上對比第一種有優勢。但如果需要實現橡皮擦的功能第一種就滿足不了了,我的一篇博文中具體介紹了橡皮擦的實現可以參看
清除canvas畫布內容--點擦除+線