following code 使用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();
}
我正在尝试模拟以下任何类型的圈子:
我已经研究过并发现了 article ,但无法应用它。
我希望绘制圆而不仅仅是出现。
有更好的方法吗?我感觉会涉及很多数学:)
P.S。我喜欢PaperJs的简单性,也许这是使用简化路径的最简单方法?
答案 0 :(得分:12)
这里已经提出了很好的解决方案。我想添加已经呈现的内容的变体 - 如果想要模拟手绘圆圈,除了一些三角函数之外没有太多选项。
我首先建议实际记录一个真正的手绘圆圈。您可以记录点以及timeStamp
,并在以后随时重现精确的绘图。您可以将其与线条平滑算法结合使用。
这里的解决方案产生如下的圆圈:
您可以像往常一样设置strokeStyle
,lineWidth
等来更改颜色,厚度等。
要绘制一个圆圈,只需致电:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
(提供callback
,因为动画使函数异步)。
代码分为两部分:
<强>初始化强>:
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
(这将使线条平滑而不是“锯齿”,因为我们之间不使用清晰但只是在顶部绘制)我们需要考虑实现顶部画布到用动画完成动画,当动画结束时我们将结果绘制到主画布。
现在我们开始看到涉及“复杂性”的一部分。这当然是因为画布是“低级”的,因为我们需要为所有东西提供所有逻辑。每次我们使用画布做更多事情时,我们基本上构建系统,而不仅仅是绘制简单的形状和图像(但这也提供了极大的灵活性)。
答案 1 :(得分: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是有意义的,它通过慢慢改变它的随机量来模拟“不稳定”的手。
有很多不同的方法可以做到这一点。我选择trig函数来简化模拟,因为速度不是这里的一个因素。
更新
例如,这会使它不那么完美:
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 )
答案 2 :(得分:3)
您的任务似乎有3个要求:
要开始使用,请查看Andrew Trice的这个漂亮的目标演示。
这个惊人的圈子是我手绘的(你现在可以笑了......!)
Andrew的演示执行了您的要求的第1步和第2步。
它可以让您使用有机外观的“画笔效果”来绘制圆形(或任何形状),而不是通常在画布中使用的通常的超精确线条。
通过在手绘点之间重复绘制画笔图像来实现“画笔效果”
以下是演示:
http://tricedesigns.com/portfolio/sketch/brush.html#
代码可以在GitHub上找到:
https://github.com/triceam/HTML5-Canvas-Brush-Sketch
安德鲁·特里斯(Andrew Trice)的演示抽取并忘记构成你圈子的线条。
你的任务是恭维你的第三个要求(记住笔画):
结果:一个手绘和程式化的圆圈,逐渐显示而不是一次性显示。
你有一个有趣的项目...如果你感到慷慨,请分享你的结果!
答案 3 :(得分:2)
面对类似的任务,我为SVG或HTML5 Canvas创建了一个卡通风格的JS绘图库。它适用于Raphael.js
,D3.js
或SVG.js
的插件或Canvas
的lib。它被称为comic.js
,可以在github找到。在其他形状中,它可以绘制您要求的类似圆圈。它基于您提到的article。
这可以产生:
答案 4 :(得分: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)