我们在上一篇文章中讲了如何绘制平滑曲线 canvas小画板——(1)平滑曲线

透明度实现荧光笔

现在我们需要加另外一种画笔效果,带透明度的荧光笔。那可能会觉得绘制画笔的时候加上透明度就可以了。我们来在原来代码上设置

ctx.globalAlpha属性为0.3,或者将strokeStyle设置为rgba的形式如rgba(55,55,55,0.3),代码如下:
<span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP<span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP
<!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

我们鼠标画线出来的效果如下,可以看到有很多重叠区域:

<span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP

对canvas有所了解的同学,知道

lineJoin和
lineCap的话可能会尝试改变这两个属性,实现之后也有同样重叠的效果。

  <span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP

 <span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP

解决荧光笔重叠问题 

为什么会有这种重叠渲染颜色的问题呢?细细品味代码,你会发现是因为每次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>

两种解决方法对比 

这两种方法都可以实现荧光笔的效果,如下截图:

<span style="color: #339966;">canvas小画板&mdash;&mdash;(1)平滑曲线</span>-LMLPHP

 第一种方法只绘制上个点和当前点,而第二种需要绘制所有线条,所以从流畅性上对比第一种有优势。但如果需要实现橡皮擦的功能第一种就满足不了了,我的一篇博文中具体介绍了橡皮擦的实现可以参看

清除canvas画布内容--点擦除+线擦除

08-06 20:29