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

alex2wong發表於2019-03-03

前幾天有很多人問到了如何繪製高德地圖那樣的導航箭頭線效果,當時想了想並不難就先用canvas 做了,然後整合到mapbox 上,遷移到其他map lib 也只需要應用相應的地理轉螢幕座標函式。

線的繪製樣式

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

各種線樣式

虛線樣式

  • 利用 CanvasRenderingContext2D.setLineDash 的方法設定虛線樣式, 接受一個陣列型別的引數([solid: number, empty:number]) ,表示實線虛線的畫素比例.

比如 ctx.setLineDash([10, 5]),就可以畫出上圖的虛線效果。發揮想象可以做出更多奇特效果。比如讓虛線動起來,有走馬燈的感覺。。

漸變線樣式

通過 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 角度一樣的。

icon pattern 的線樣式

最後這個算是綜合的應用,我寫了些canvas functions 放到了之前的canvasOverlay 裡面去用,可以方便的整合到各種支援canvas 的lib 裡面用。 基本思路就是:(懶得畫圖了,思路比較簡單)

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

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

需要注意的是,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;
}

複製程式碼

就簡單寫到這裡,整合mapbox 的DEMO

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

因為之前也寫過一些canvas 的文章,比如繪製風向圖,希望形成一個系列,所以這次就叫第二篇,下一篇應該是icon與傾斜角的貼合實踐

相關文章