1. 程式人生 > >canvas小畫板——(2)熒光筆效果

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>
View Code

我們滑鼠畫線出來的效果如下,可以看到有很多重疊區域:

 

對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畫布內容--點擦除+線