Canvas繪製圓點線段

最近一個小夥遇到一個需求,客戶需要繪製圓點樣式的線條。 大致效果是這樣的:

![圓點線](https://upload-images.jianshu.io/upload_images/6271001-32e0a4d4d5db7d9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 思路一:計算並使用arc填充

他自己實現了一種思路,然後諮詢我有沒有更好的思路。 先看看他的思路是如何實現的,大致代碼如下:

```
// 繪製圓點線,通過計算在線條上進行插值運算,計算出需要繪製圓點的一系列點位
// 然後調用drawDot方法繪製圓點
 function DrawDottedLine(x1,y1,x2,y2,dotRadius,dotCount,dotColor){
      var dx=x2-x1;
      var dy=y2-y1;
      var spaceX=dx/(dotCount-1);
      var spaceY=dy/(dotCount-1);
      var newX=x1;
      var newY=y1;
      for (var i=0;i<dotCount;i++){
              drawDot(newX,newY,dotRadius,dotColor);
              newX+=spaceX;
              newY+=spaceY;              
       }
       drawDot(x1,y1,3,"red");
       drawDot(x2,y2,3,"red");
    }

// 繪製圓點
    function drawDot(x,y,dotRadius,dotColor){
        ctx.beginPath();
        ctx.arc(x,y, dotRadius, 0, 2 * Math.PI, false);
        ctx.fillStyle = dotColor;
        ctx.fill();              
    }

```

通過上面的簡單的示意代碼可以看出,繪製邏輯是通過計算直線之間的點位,然後再相應的點上面繪製圓形。
該方法最終可以達到效果,可是有如下問題:

*   存在性能問題
*   如果是貝塞爾曲線曲線,可能會涉及到複雜的運行。 貝塞爾曲線的相關知識,可以參考下文進行了解:
    [深入理解貝塞爾曲線](https://xiaozhuanlan.com/topic/9506147283)

當然此思路在繪製一些更加複雜的效果的時候,可能會有用。比如沿線繪製五角星,其他任意形狀或者圖片等等。或者要繪製的是圓圈而不是填充的圓形的效果,也需要使用此方法。

但是如果只是繪製圓點線,我們可以使用更加簡便的方法,主要思路就是使用setLineDash方法+lineCap設置

# 思路二 setLineDash方法+lineCap設置

## lineCap介紹

CanvasRenderingContext2D.lineCap 是 Canvas 2D API 指定如何繪製每一條線段末端的屬性。有3個可能的值,分別是:butt, round and square。默認值是 butt。

使用時,直接賦值即可:

```
ctx.lineCap = "butt";
ctx.lineCap = "round";
ctx.lineCap = "square";

```

下面從左到右分別是butt, round ,square的效果:

![lineCap](https://upload-images.jianshu.io/upload_images/6271001-6c0068f83d553ce2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看出 “round”和“square”都是在原本繪製得線段之外擴展了一個半圓和一個矩形,這點在後面會用到。

相關知識,可以參考這篇文章:
[canvas基礎知識回顧](https://xiaozhuanlan.com/topic/5473801692)

## setLineDash介紹

Canvas 2D API的[`CanvasRenderingContext2D`](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)接口的**`setLineDash()`**方法在填充線時使用虛線模式。 它使用一組值來指定描述模式的線和間隙的交替長度。

語法如下:

```
ctx.setLineDash(segments);
//segments數組。一組描述交替繪製線段和間距(座標空間單位)長
//度的數字。 如果數組元素的數量是奇數, 數組的元素會被複制並重
//復。例如, `[5, 15, 25]` 會變成 `[5, 15, 25, 5, 15, 25]。`</dd>

```

## 繪製圓點線原理

有了上面兩個知識點,只需要把兩者結合起來,就可以繪製出圓點線,我們首先使用ctx.setLineDash方法把線段分成一段一段得虛線。 然後把lineCap設置爲“round”,我們就會得到一段一段得端點是半圓得小線段,代碼如下:

```
                ctx.beginPath();
        ctx.lineWidth = 5;
        ctx.lineCap = "round"
        ctx.setLineDash([10, 30]);
        ctx.moveTo(0, 50);
        ctx.lineTo(300, 50);
        ctx.lineTo(300, 200);
        ctx.quadraticCurveTo(500,300,400,400);
        ctx.stroke();

```

最終繪製得效果如下圖所示:

![image.png](https://upload-images.jianshu.io/upload_images/6271001-79a35399ccb1ebf5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

到此,又朋友可能有疑問,這個也不是圓點線得效果。 其實只需要把上面得代碼稍微得修改,讓實線線段本身得長度變成0即可,修改代碼:

```
...
ctx.setLineDash([0, 30]);
...

```

最終繪製得效果如下圖所示:

![image.png](https://upload-images.jianshu.io/upload_images/6271001-e51127a9f9cf64c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

結合前面 lineCap 得知識點,相信很容易理解。

如果要繪製方塊得效果,也是很容易得,只需要把lineCap 改成"square" 即可:

```
                ctx.beginPath();
        ctx.lineWidth = 10;
        ctx.lineCap = "square"
        ctx.setLineDash([0, 30]);
        ctx.moveTo(0, 50);
        ctx.lineTo(300, 50);
        ctx.lineTo(300, 200);
        ctx.quadraticCurveTo(500,300,400,400);
        ctx.stroke();

```

效果如下:

![image.png](https://upload-images.jianshu.io/upload_images/6271001-01431c0dda2c53e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

此處有人可能會說,lineCap 爲“butt”同樣可以做出方塊得效果,只需要調整setLineDash得參數即可:

```
        ctx.beginPath();
        ctx.lineWidth = 10;
        ctx.lineCap = "butt"
        ctx.setLineDash([10, 30]);
        ctx.moveTo(0, 50);
        ctx.lineTo(300, 50);
        ctx.lineTo(300, 200);
        ctx.quadraticCurveTo(500,300,400,400);
        ctx.stroke();

```

確實如此,但是使用“square” 得情況下setLineDash函數的參數的一個值始終是0,而“butt” 的情況下,setLineDash函數的參數的第一個參數值需要隨着lineWidth變化而變化,很不方便,而且“butt”的情況下,還會出現尾部可能不是一個方塊的效果,如下圖:

![image.png](https://upload-images.jianshu.io/upload_images/6271001-8bc121d2616940f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

# 擴展

如果要繪製如下得線條樣式,應該怎麼做呢:

![image.png](https://upload-images.jianshu.io/upload_images/6271001-fe4112eccd5c1de7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

其實也很簡單,就是把同一條線段用不同得lineDash和lineCap繪製兩次即可,代碼如下:

```
  // 繪製圓點
    ctx.save();
    ctx.beginPath();
    ctx.lineCap = "round";
    ctx.setLineDash([0, 80]);
    ctx.lineWidth = 20;
    ctx.moveTo(50, 50);
    ctx.lineTo(400, 50);
    ctx.lineTo(400, 400);
    ctx.quadraticCurveTo(750, 450, 800, 800);
    ctx.stroke();
    //繪製直線
    ctx.lineCap = "butt";
    ctx.setLineDash([0, 20, 40, 20]);
    ctx.lineWidth = 10;
    ctx.moveTo(50, 50);
    ctx.lineTo(400, 50);
    ctx.lineTo(400, 400);
    ctx.quadraticCurveTo(750, 450, 800, 800);
    ctx.stroke();

```

需要注意的是繪製第二段的時候,需要調整好lineDash的segments的值。

歡迎關注公衆號“ITman彪叔”。彪叔,擁有10多年開發經驗,現任公司系統架構師、技術總監、技術培訓師、職業規劃師。熟悉Java、JavaScript、Python語言,熟悉數據庫。熟悉java、nodejs應用系統架構,大數據高併發、高可用、分佈式架構。在計算機圖形學、WebGL、前端可視化方面有深入研究。對程序員思維能力訓練和培訓、程序員職業規劃有濃厚興趣。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章