继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

canvas绘制箭头路径效果

BIG阳
关注TA
已关注
手记 424
粉丝 70
获赞 457

前几天有很多人问到了如何绘制高德地图那样的导航箭头线效果,当时想了想并不难就先用canvas 做了,然后集成到mapbox 上,迁移到其他map lib 也只需要应用相应的地理转屏幕坐标函数。

线的绘制样式

在canvas 的应用中我们经常会遇到各种线样式的绘制,比如虚线,渐变线,带pattern 线(箭头,铁轨图标等),或者虚线与pattern 的动画效果。如下图所示,总结下实现方法。

700

各种线样式

虚线样式

  • 利用 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

445

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 的透视去做,前者应该更简单些。有空再实践下



作者:四爷在此
链接:https://www.jianshu.com/p/e5ebb8ca4aeb


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP