canvas 奇巧淫技(二)繪製箭頭路徑效果

前幾天有不少人問到了如何繪製高德地圖那樣的導航箭頭線效果,當時想了想並不難就先用canvas 作了,而後集成到mapbox 上,遷移到其餘map lib 也只須要應用相應的地理轉屏幕座標函數。git

線的繪製樣式

在canvas 的應用中咱們常常會遇到各類線樣式的繪製,好比虛線,漸變線,帶pattern 線(箭頭,鐵軌圖標等),或者虛線與pattern 的動畫效果。以下圖所示,總結下實現方法。github

各類線樣式

虛線樣式

  • 利用 CanvasRenderingContext2D.setLineDash 的方法設置虛線樣式, 接受一個數組類型的參數([solid: number, empty:number]) ,表示實線虛線的像素比例.

好比 ctx.setLineDash([10, 5]),就能夠畫出上圖的虛線效果。發揮想象能夠作出更多奇特效果。好比讓虛線動起來,有走馬燈的感受。。canvas

漸變線樣式

經過 createLinearGradient 函數建立漸變,而後設定其漸變色段, 賦值給strokeStyle,漸變效果如開頭圖所示數組

// 建立一個從左到右的漸變,從綠色漸變到幾乎透明,
var gradient = ctx.createLinearGradient(0, 0, 600, 0);
gradient.addColorStop(0, "rgba(0,255,100,0.9)");
gradient.addColorStop(1, "rgba(255,255,255,0.1)");
ctx.strokeStyle = gradient;
複製代碼

有個問題就是若是須要漸變方向符合線條走向,這是常見需求,只須要提早算下每條線的範圍和方向,建立對應的LinearGradient 便可,其實相似於下面的箭頭繪製,須要反算 atan 角度同樣的。bash

icon pattern 的線樣式

最後這個算是綜合的應用,我寫了些canvas functions 放到了以前的canvasOverlay 裏面去用,能夠方便的集成到各類支持canvas 的lib 裏面用。 基本思路就是:(懶得畫圖了,思路比較簡單)ide

  • 每個有向線段根據startPnt, endPnt 座標反算 atan 弧度角函數

  • 設定一個stepSize,在線段上生成繪製圖標的座標,把ctx.原點平移到這個點,而且旋轉弧度角去繪製 圖標的偏向(熟悉canvas 的應該都明白,經過操做canvas 座標軸去繪製旋轉要素)post

須要注意的是,atan弧度角的計算在第二三象限,會跟第一四象限混淆。好比向左下角的有向線的向量是兩個負值,可是tan 值是正的,跟第一象限同樣,因此反算的時候也會算出來小於90度的角,其實是大於180 的角度了,須要 + Math.PI優化

image.png

大概的繪製過程,code as follow:動畫

function generatePoints(startP, endP, stepSize = 30, ctx, aniOffset = 0.5, img) {
    let radA = Math.atan((endP[1] - startP[1]) / (endP[0] - startP[0]));
    if ((endP[0] - startP[0]) < 0) {
        radA += Math.PI;
    }
    const dist = calcDist(startP, endP);
    let points = [];
    const steps = dist / stepSize;

    const drawImg = (pX, pY) => {
        if (img && ctx) {
            ctx.save();
            ctx.translate(pX , pY);  // consider img position and imgWidth/Height.
            ctx.rotate(radA);
            ctx.drawImage(img, -img.width / 2,  -img.width/2);
            ctx.restore();
        }
    }

    // gen points by stepSize.. if enable corner arrow, start s with (0~1) float number.
    for (let s = aniOffset; s <= steps; s += 1) {
        const pX = Math.round(startP[0] + s * stepSize * Math.cos(radA));
        const pY = Math.round(startP[1] + s * stepSize * Math.sin(radA));
        points.push([pX, pY]);
        drawImg(pX, pY);
    }
    // console.warn(`icon Number: ${points.length}`);
    return points;
}

複製代碼

最近還有個問題提到比較多,關於canvas 上的圖標如何貼合地圖的傾斜,這也是個視覺上的問題。大抵上能夠經過CSS3d 或者 canvas 的透視去作,前者應該更簡單些。有空再實踐下。

由於以前也寫過一些canvas 的文章,好比繪製風向圖,但願造成一個系列,因此此次就叫第二篇,下一篇應該是icon與傾斜角的貼合實踐

最近Update (2019-10):

  • 從新優化了icon pattern 計算和渲染的過程,使得沿線的icon 位置計算更加高效流暢,能夠作到60fps的計算+渲染,而且自帶流暢的箭頭過渡動效 在線體驗DEMO
  • 圖標貼合地圖傾斜角的問題,以前就解決了,一直沒有貼出來。其實就是把map View 的傾角和旋轉角同步給 icon,或者 marker.很簡單,無須贅述 在線體驗
相關文章
相關標籤/搜索