16

以下代码使用 jQuery 在 HTML 5 Canvas 中创建一个圆圈:

代码:

//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");

DrawCircle(75, 75, 20);

//draw a circle
function DrawCircle(x, y, radius)
{
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI*2, true); 
    ctx.fillStyle = 'transparent';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#003300';
    ctx.stroke();
    ctx.closePath();
    ctx.fill();
}

我正在尝试模拟以下任何类型的圆圈:

例子

我已经研究并找到了这篇文章,但无法应用它。

我希望绘制圆圈而不是仅仅出现。

有一个更好的方法吗?我感觉这将涉及很多数学:)

PS 我喜欢PaperJs的简单性,也许这将是使用它的简化路径的最简单方法?

4

4 回答 4

16

这里已经有很好的解决方案。我想添加已经呈现的内容的变体 - 如果想要模拟手绘圆圈,除了一些三角函数之外没有太多选择。

我首先建议实际记录一个真实的手绘圆圈。您可以timeStamp在以后的任何时间记录点以及复制精确的绘图。您可以将其与线平滑算法结合使用。

这里的解决方案会产生如下圆圈:

快照

您可以像往常一样通过设置strokeStyle等来更改颜色、厚度等。lineWidth

要画一个圆圈,只需调用:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);

callback在动画使函数异步时提供)。

代码分为两段:

  1. 生成点
  2. 动画点

初始化

function handDrawCircle(ctx, cx, cy, r, rounds, callback) {

    /// rounds is optional, defaults to 3 rounds    
    rounds = rounds ? rounds : 3;

    var x, y,                                      /// the calced point
        tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
        dx = Math.random() * tol * 0.75,           /// "bouncer" values
        dy = Math.random() * tol * 0.75,
        ix = (Math.random() - 1) * (r * 0.0044),   /// speed /incremental
        iy = (Math.random() - 1) * (r * 0.0033),
        rx = r + Math.random() * tol,              /// radius X 
        ry = (r + Math.random() * tol) * 0.8,      /// radius Y
        a = 0,                                     /// angle
        ad = 3,                                    /// angle delta (resolution)
        i = 0,                                     /// counter
        start = Math.random() + 50,                /// random delta start
        tot = 360 * rounds + Math.random() * 50 - 100,  /// end angle
        points = [],                               /// the points array
        deg2rad = Math.PI / 180;                   /// degrees to radians

在主循环中,我们不会随机反弹,而是以随机值递增,然后以该值线性递增,如果我们处于界限(容差),则将其反转。

for (; i < tot; i += ad) {
    dx += ix;
    dy += iy;

    if (dx < -tol || dx > tol) ix = -ix;
    if (dy < -tol || dy > tol) iy = -iy;

    x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
    y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);

    points.push(x, y);
}

在最后一段中,我们只渲染我们所拥有的点。

速度由da上一步中的(增量角)确定:

    i = 2;

    /// start line    
    ctx.beginPath();
    ctx.moveTo(points[0], points[1]);

    /// call loop
    draw();

    function draw() {

        ctx.lineTo(points[i], points[i + 1]);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(points[i], points[i + 1]);

        i += 2;

        if (i < points.length) {
            requestAnimationFrame(draw);
        } else {
            if (typeof callback === 'function')
                callback();
        }
    }
}

提示:要获得更逼真的笔触,您可以减少globalAlpha到例如0.7.

但是,要使其正常工作,您需要先将实体绘制到屏幕外画布,然后将该屏幕外画布blit到globalAlpha每个帧的主画布(具有集合),否则笔划将在每个点之间重叠(其中看起来不太好)。

对于正方形,您可以使用与圆形相同的方法,但不是使用半径和角度,而是将变化应用于一条线。偏移增量以使线不直。

我稍微调整了这些值,但可以随意调整它们以获得更好的结果。

要使圆圈“倾斜”一点,您可以先将画布旋转一点:

rotate = Math.random() * 0.5;

ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);

当循环结束时:

if (i < points.length) {
    requestAnimationFrame(draw);
} else {
   ctx.restore();
}

(包含在上面链接的演示中)。

圆圈看起来更像这样:

快照倾斜

更新

要处理提到的问题(评论字段太小:-)):实际上制作动画线条有点复杂,尤其是在这样的情况下,您需要进行圆周运动以及随机边界。

参考。评论点 1:公差与半径密切相关,因为它定义了最大波动。我们可以修改代码以采用ix/iy基于半径的容差(并且因为它们定义了其变化的“速度”有多快)。这就是我所说的调整,以找到适用于所有尺寸的价值/最佳点。圆圈越小,变化越小。可以选择将这些值指定为函数的参数。

第 2 点:因为我们正在为圆设置动画,所以函数变得异步。如果我们一个接一个地画两个圆圈,它们会弄乱画布,因为新的点被添加到两个圆圈的路径中,然后被纵横交错地抚摸。

我们可以通过提供回调机制来解决这个问题:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);

然后当动画完成时:

if (i < points.length) {
    requestAnimationFrame(draw);

} else {
    ctx.restore();
    if (typeof callback === 'function')
        callback();  /// call next function
}

代码原样会遇到的另一个问题(请记住,代码仅作为示例而不是完整的解决方案:-))是粗线:

当我们逐段单独绘制时,画布不知道如何计算线相对于前一段的对接角度。这是路径概念的一部分。当您用几段画布描边路径时,知道对接(线的末端)将处于什么角度。所以在这里我们要么从开始点到当前点画线,要么在两者之间或只做一个小lineWidth值。

当我们使用时clearRect(这将使线条平滑而不是“锯齿状”,就像我们之间不使用清晰而只是在顶部绘制时一样)我们需要考虑实现一个顶部画布来执行动画以及动画何时完成我们将结果绘制到主画布上。

现在我们开始看到所涉及的“复杂性”的一部分。这当然是因为画布是“低级”的,因为我们需要为所有内容提供所有逻辑。每次我们用画布做更多的事情时,我们基本上都是在构建系统,而不仅仅是绘制简单的形状和图像(但这也提供了很大的灵活性)。

于 2013-09-09T02:03:12.443 回答
8

以下是我为这个答案创建的一些基础知识:

http://jsfiddle.net/Exceeder/TPDmn/

基本上,当你画一个圆圈时,你需要考虑手部的缺陷。因此,在以下代码中:

var img = new Image();
img.src="data:image/png;base64,...";

var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
  ctx.drawImage(img, x, y);
}

for (var i=0; i<500; i++) {
    var radiusError = +10 - i/20;
    var d = 2*Math.PI/360 * i;
    draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}

注意当角度(和位置)增加时垂直radiusError如何变化。欢迎您使用这个小提琴,直到您“感觉”到什么组件在做什么。例如,将另一个组件引入 radiusError 是有意义的,它通过缓慢地改变我的随机数量来模拟“不稳定”手。

有许多不同的方法可以做到这一点。我选择三角函数是为了模拟的简单性,因为速度在这里不是一个因素。

更新:

例如,这将使它不那么完美:

var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);

显然,圆的中心在 (200,200),因为用三角函数画圆(垂直半径为 RY 和水平半径为 RX 的省略号)的公式为

x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )
于 2013-09-08T17:52:03.863 回答
3

您的任务似乎有 3 个要求:

  1. 手绘的形状。
  2. “有机”而不是“超精确”的中风。
  3. 逐步显示圆圈,而不是一次性显示。

要开始使用,请查看 Andrew Trice 的这个漂亮的目标演示。

这个神奇的圆圈是我手绘的(你现在可以笑了……!)

我用安德鲁的技术创造了一个惊人的圈子

Andrew 的演示完成了您要求的第 1 步和第 2 步。

它允许您使用有机外观的“画笔效果”来手绘一个圆圈(或任何形状),而不是通常在画布中使用的超精确线条。

它通过在手绘点之间重复绘制画笔图像来实现“画笔效果”

这是演示:

http://tricedesigns.com/portfolio/sketch/brush.html#

代码可在 GitHub 上找到:

https://github.com/triceam/HTML5-Canvas-Brush-Sketch

Andrew Trice 的演示绘制并忘记了构成您的圆圈的线条。

你的任务是暗示你的第三个要求(记住笔画):

  • 手绘一个自己的圈子,
  • 将组成圆圈的每个线段保存在数组中,
  • 使用 Andrew 的风格化画笔技术“播放”这些片段。

结果:一个手绘的风格化圆圈,逐渐出现,而不是一次全部出现。

你有一个有趣的项目……如果你觉得慷慨,请分享你的结果!

于 2013-09-08T17:06:31.097 回答
1

在此处查看现场演示。 也可作为要点

<div id="container">
    <svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>

#container {
  width:500px;
  height:300px;
}
path.ln {
  stroke-width: 3px;
  stroke: #666;
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  -webkit-animation: dash 5s ease-in forwards;
  -moz-animation:dash 5s ease-in forwards;
  -o-animation:dash 5s ease-in forwards;
  animation:dash 5s ease-in forwards;
}

@keyframes dash {
  to { stroke-dashoffset: 0; }
}

function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {

    var c = 0.551915024494;
    var atan = Math.atan(c)
    var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
    var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
    var path = 'M';

    path += [r * Math.sin(el), r * Math.cos(el)];
    path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];

    for (var i = 0; i < 4; i++) {
        el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
        r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
        path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
        path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
    }

    return path;
}

function cX(λ_min, λ_max, el_min, el_max) {
    var el = (el_min + Math.random()*(el_max - el_min));
    return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}

function canvasArea() {
    var width = Math.floor((Math.random() * 500) + 450);
  var height = Math.floor((Math.random() * 300) + 250);
    $('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));

setTimeout(function() { location = '' } ,5000)
于 2014-09-29T08:22:04.100 回答