及时绘制 HTML5/Javascript 画布路径

2022-01-17 00:00:00 canvas javascript html5-canvas

假设我有一条路径:

var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 20);
context.lineTo(200, 160);
context.quadraticCurveTo(230, 200, 250, 120);
context.bezierCurveTo(290, -40, 300, 200, 400, 150);
context.lineTo(500, 90);
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();

这会一次性打印路径:

如何将路径拆分为给定长度的子路径?例如:context.splitCurrentPathIntoSubPath(0, 0.75) 应该只返回路径的前 3/4.

How can I split the path into subpaths of given length? For example: context.splitCurrentPathIntoSubPath(0, 0.75) should return only the first 3/4 of the path.

我想用它来实现动画.如果有更简单的方法,也欢迎.

I would like to use this to realise an animation. If there is an easier method, it is also welcome.

推荐答案

我在使用 D3.js 为 SVG 弧线制作动画时遇到了类似的问题.我的解决方案借鉴了这一点.它不是最直观的,但通常用于 D3 动画.它需要仔细设置虚线偏移和线长.CSS Tricks 很好地解释了这里的技术,我强烈建议在查看我的代码之前阅读它.

I came across a similar problem when animating SVG arcs using D3.js. My solution borrows from that. It isn't the most intuitive, but is commonly used in D3 animations. It requires careful setting of the dash offset and line length. CSS Tricks gives a good explanation of the technique here which I strongly recommend reading before viewing my code.

我已经用这种技术修改了上面的 JSFiddle 在此处为您的线路实施.请注意,即使线路自行循环,这也会起作用.

I have modified the above JSFiddle with this technique implemented for your line here. Note this will work even if the line loops back on itself.

关于行长的说明:

此实现要求您知道行的大​​致长度,以便您可以将长度 var 设置为大于它.对于贝塞尔曲线和二次曲线,这很棘手,但仍然可以完成(这个 SO 问题看起来很有希望).对于我的演示,我通过反复试验发现你的大约是 608 像素.将长度设置为 10000 可能会确保您的线条始终正确绘制,但代价是每毫秒调用大量不必要的间隔回调.底线是:如果您关心性能,请找出贝塞尔公式;如果不这样做,请将该变量设置为高.

This implementation requires you to know the approx length of your line so that you can set the length var to be greater than it. For bezier and quadratic curves this is tricky but can nevertheless be done (this SO question looks promising). For my demo I used trial and error to find that yours was about 608px. Setting length to 10000 will probably ensure your lines always draw correctly, but at the cost of having lots of needless interval callbacks called every millisecond. The bottom line is: if you care about performance, figure out the bezier formula stuff; if you don't, set that variable high.

代码:

HTML

<body>
    <canvas id="canvas" width="500" height="500">
        webgl couldn't be started
    </canvas>
</body>

JavaScript

canvasHolder = document.getElementById( 'canvas' );
context = canvasHolder.getContext('2d');

context.fillStyle = 'white';
var w = canvasHolder.width, h = canvasHolder.height;
context.fillRect( 0, 0, w, h);

//set the direction the line draws in
//1->ltr | -1->rtl
var dir = -1;
//IMPORTANT: this must be set to greater than the length
//of the line
var length = 608;
//the speed of the line draw
var speed = 1;

var progress = 0;
var lineInterval;

//Go!
context.globalCompositeOperation='copy';
drawLine();

function drawLine() {
    //this clears itself once the line is drawn
    lineInterval = setInterval(updateLine, 1);
}

function updateLine() {
    //define the line
    defineLine();

    if(progress<length)
    {
      progress+=speed;
      moveDash(progress, dir);
    } else {
      clearInterval(lineInterval);
    }

}

function defineLine() {
    context.beginPath();
    context.moveTo(100, 20);
    context.lineTo(200, 160);
    context.quadraticCurveTo(230, 200, 250, 120);
    context.bezierCurveTo(290, -40, 300, 200, 400, 150);
    context.lineTo(500, 90);
    context.lineWidth = 5;
    context.strokeStyle = 'blue';
}

function moveDash(frac, dir) {
    //default direction right->left
    var dir = dir || -1 
    context.setLineDash([length]);
    context.lineDashOffset = dir*(frac+length);
    context.stroke();
}

相关文章