使用SVG的弧形路径绘制圆形

184

使用SVG路径,我们可以绘制99.99%的圆形并显示出来,但是当它是99.99999999%的圆形时,圆形就不会显示出来。如何解决?

以下SVG路径可以绘制99.99%的圆形:

var paper = Raphael(0, 0, 300, 800);

// Note that there are supposed to be 4 arcs drawn, but you may see only 1, 2, or 3 arcs depending on which browser you use


paper.path("M 100 100 a 50 50 0 1 0 35 85").attr({stroke: "#080", opacity: 1, "stroke-width" : 6})  // this is about 62.5% of a circle, and it shows on most any browsers
    
paper.path("M 100 210 a 50 50 0 1 0 0.0001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6})    // this one won't show anything if it is IE 8's VML, but will show if it is Chrome or Firefox's SVG.  On IE 8, it needs to be 0.01 to show
    
paper.path("M 100 320 a 50 50 0 1 0 0.0000001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6})   // this one won't draw anything at all, unless you change the 0.0000001 to 0.0001 on Chrome or Firefox... Safari will show it though...
    
paper.path("M 100 430 a 50 50 0 1 0 0 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6})   // this is 100% of a circle...  even Safari won't show it
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>

M 100 100 a 50 50 0 1 0 0.00001 0

但当它是圆的99.99999999%时,什么也不会显示出来吗?

M 100 100 a 50 50 0 1 0 0.00000001 0    

同样的,对于一个圆来说也是如此(它仍然是一段弧线,不是吗,只不过是非常完整的一段弧线)

M 100 100 a 50 50 0 1 0 0 0 

如何解决这个问题?原因是我使用了一个函数来画圆弧的百分比,如果我需要"特殊处理"一个99.9999%或100%的圆弧来使用圆形函数,那就有点傻了。

再次声明,上面有一个测试用例(如果在IE 8上是VML,则甚至第二个圆也不会显示...你必须将其更改为0.01)。


更新:

这是因为我们的系统中正在渲染得分的弧线,所以3.3分得到1/3的圆。0.5得到一半的圆,而9.9分得到99%的圆。但是,如果我们的系统中有得分是9.99怎么办?我是否需要检查它是否接近于99.999%的圆,然后相应地使用arc函数或circle函数?那么对于得分为9.9987的情况呢?该使用哪一个?需要知道哪些得分将映射到一个“太完整的圆”并切换到圆形函数,以及何时是“某个99.9%的”圆或9.9987的分数,然后使用圆弧函数,这是荒谬的。


1
这两个链接指向同一个页面,在Safari中可以正常工作。 - Mark Bessey
对的,同一个链接,我只是想让人们更早地看到测试用例,所以在问题开头添加了链接。没错,Safari会这样做,多好啊……Chrome和Firefox不会……有点奇怪,因为Safari和Chrome都是Webkit……但SVG引擎是否依赖于Webkit呢? - nonopolarity
@Marcin 看起来怎么样?你看到了4个弧线还是2个弧线?你甚至看过代码吗? - nonopolarity
2
一个更新的jsfiddle,包含Raphael作为库,以解决跨域错误加载raphael.js的问题:http://jsfiddle.net/DFhUF/1381/ - ericsoco
http://codepen.io/dcdev/pen/upjDy - davidcondrey
显示剩余3条评论
12个回答

513

更新:

根据 @loominade 的反馈,我进行了调整,使得圆形的绘制顺序为“东、南、西、北”,从而使 stroke-dashoffsetstartOffset 与原生的 circle 元素更加一致。


我曾经有过类似的困惑,后来找到了这个解决方案:

<path 
    d="
    M cx cy
    m r, 0
    a r,r 0 1,0 -(r * 2),0
    a r,r 0 1,0  (r * 2),0
    "
/>

换句话说,这个:
<circle cx="100" cy="100" r="75" />

可以通过以下方式实现:

  <path 
        d="
        M 100, 100
        m 75, 0
        a 75,75 0 1,0 -150,0
        a 75,75 0 1,0  150,0
        "
  />

诀窍在于有两个弧,第二个弧从第一个弧结束的地方开始,并使用负直径返回到原始弧起点。

不能在一个弧中完成整个圆的原因(我只是猜测)可能是因为您会告诉它从自身(假设为150,150)绘制一条弧到自身(150,150),这将被渲染为“哦,我已经在那里了,不需要弧!”。

我提供的解决方案的好处是:

  1. 可以直接从圆形转换为路径,
  2. 两个弧线之间没有重叠(如果您正在使用标记或图案等可能会引起问题)。 这是一条干净的连续线,尽管分成两部分绘制。

如果他们允许文本路径接受形状,则所有这些都无关紧要。 但我认为他们避免使用该解决方案,因为像圆这样的形状元素在技术上没有“起点”。

片段演示:

circle, path {
    fill: none;
    stroke-width: 5;
    stroke-opacity: .5;
}

circle {
    stroke: red;
}
path {
    stroke: yellow;
}
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
     width="220px" height="220px">

      <circle cx="100" cy="100" r="75" />

      <path 
            d="
            M 100, 100
            m 75, 0
            a 75,75 0 1,0 -150,0
            a 75,75 0 1,0  150,0
            "
      />

</svg>

更新:

如果你使用路径作为textPath的引用,并且想要文本呈现在弧形的外边缘,那么你可以使用完全相同的方法,但是将扫描标志从0更改为1,以便将路径的外部视为表面而不是内部(将1,0视为某人坐在中心点并绕着自己画一个圆圈,而将1,1视为某人在半径距离处围绕中心走动并拖着粉笔旁边,如果这有任何帮助的话)。以下是代码与更改:

<path 
    d="
    M cx cy
    m r, 0
    a r,r 0 1,1 -(r * 2),0
    a r,r 0 1,1  (r * 2),0
    "
/>

1
+1,感谢提供公式。当然,前两个命令可以合并。 - John Gietzen
16
我认为问题不在于“哦,我已经到了,不需要弧线!” ,因为通过提供1作为大弧标志,您明确告诉引擎您希望它走更远的路。真正的问题在于有无限多个圆满足通过您的点的约束条件。这就解释了为什么当您想要360*弧时,引擎不知道该怎么办。它不能处理359.999的原因是这是一种数值上不稳定的计算,虽然在技术上确实有一个圆符合请求,但它可能不是您想要的那个圆。 - qbolec
1
“像圆形这样的形状元素在技术上并没有“起始”点”这种说法实际上是不正确的。SVG规范绝对指定了所有形状的起始点。它必须这样做才能使虚线数组在形状上起作用。” - Paul LeBeau
1
画两个弧线怎么算是hack呢?一个圆就是两个弧线。而对我来说,画99.99%的弧线才是hack。因此需要首先进行讨论。 - Anthony
1
这是一个被函数化和压缩的代码:function circPath(x,y,r){return['M',x,y,'m',-r,'0a',r,r,0,1,0,r*2,'0a',r,r,0,1,0,-r*2,0].join(' ')} - ashleedawg
显示剩余9条评论

40

同样适用于XAML的弧形。只需使用Z闭合99.99%的弧形,就可以得到一个圆形!


你能关闭 jsfiddle 示例中的第三个案例并使其正常工作吗? - nonopolarity
将值降低到0.0001,是的。问题似乎与各种浏览器对WebKit的实现有关,而不是SVG本身。但这就是在SVG / XAML的弧组件中制作圆的方法。 - Todd Main
啊,作弊,总是最好的解决方案。仍然需要处理100%的情况,但只需通过“向下取整”到99.999%而不是使用一个完全不同的代码分支来处理。 - SimonR
@MedetTleukabiluly,你在问题中有一个弧形符号,只需在末尾添加z即可。您可能需要删除零,例如在最新的Chrome浏览器中,我必须写入0.0001才能使其正常工作。如果您正在寻找路径字符串的放置位置,请查看MDN上的路径 - TWiStErRob
1
这是一种相当取巧的方法,而且你不会得到一个完美的圆。相反,看下面Anthony提供的解决方案(两个半圆可以弥补它)。 - Anton
显示剩余2条评论

33
关于安东尼的解决方案,这里是一个获取路径的函数:
function circlePath(cx, cy, r){
    return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,1 '+(r*2)+',0 a '+r+','+r+' 0 1,1 -'+(r*2)+',0';
}

1
只是想说,这个功能在我搜索了3个小时后拯救了我的生命。 - Kristiyan
1
很高兴听到这个消息,@Kristiyan - Anton Matiash

14

一个完全不同的方法:

除了通过路径来指定SVG中的弧线,您还可以使用圆形元素并指定stroke-dasharray,伪代码如下:

with $score between 0..1, and pi = 3.141592653589793238

$length = $score * 2 * pi * $r
$max = 7 * $r  (i.e. well above 2*pi*r)

<circle r="$r" stroke-dasharray="$length $max" />

相较于多弧路径法,简单是其主要优点(例如,在脚本编写时,只需插入一个值即可完成任何弧长的操作)。

弧线始于最右侧点,并可使用旋转变换进行移位。

注意:Firefox 存在一种奇怪的 bug,当旋转角度超过 90 度或更多时,将被忽略。因此,若想从顶部开始绘制弧线,请使用:

<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />

请注意,此解决方案仅适用于您不使用任何类型的填充并且只需要没有偏差的弧段的情况。如果您要绘制扇形,则甚至需要一个新的SVG路径节点,否则可以在单个路径标记中处理。 - mxfh
@mxfh 不是很准确,如果你将笔画宽度设为r值的两倍,就可以得到完美的扇形。 - mvds
使用 stroke-dasharray="0 10cm 5cm 1000cm" 可以避免转换。 - stenci
@stenci 是的,但缺点是您无法在dasharray中有一个参数来从0%到100%填充进行缩放(这是我的一个目标之一)。 - mvds

9

在Anthony和Anton的回答基础上,我增加了旋转生成的圆的能力,而不影响其整体外观。如果您正在使用路径进行动画,并且需要控制其开始位置,则此功能非常有用。

function(cx, cy, r, deg){
    var theta = deg*Math.PI/180,
        dx = r*Math.cos(theta),
        dy = -r*Math.sin(theta);
    return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}

这正是我所需要的。我正在使用GreenSock Morph插件将圆形路径变形为矩形路径,并需要轻微旋转才能使其看起来完美。 - RoboKozo

6
我在这里创建了一个 jsfiddle,来实现它:
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}

function describeArc(x, y, radius, startAngle, endAngle){

var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);

var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

var d = [
    "M", start.x, start.y, 
    "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");

return d;       
}
console.log(describeArc(255,255,220,134,136))

链接

你需要做的就是更改console.log的输入,并在控制台中获取结果。


这正是我在寻找的。最佳答案在此!赞同这位朋友。 - Måns Dahlström
你救了我的命 :) 感谢你提供这个精彩的答案!我找了整整一天! - Soley

6

对于像我这样寻找椭圆属性转换为路径的人:

const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0 Z`

5

Adobe Illustrator使用类似SVG的贝塞尔曲线,对于圆形它创建四个点。你可以使用两个椭圆弧命令创建一个圆形...但是在SVG中,我会使用<circle />来创建圆形 :)


你可以使用两个椭圆弧命令创建一个圆吗?这是什么意思?难道不能只用一个命令吗?至少让它从(0,0)到(0.01,0),这样就能渲染出来了。如果要使用“circle”,请参见问题中的更新部分。 - nonopolarity
不,我认为你不能只使用一个。你已经证明了你不能这样做,并且在HTML5 Canvas弧中也存在类似的问题。 - Phrogz
2
@動靜能量 正確,需要兩個弧形路徑(或者類似的不太美觀的解決方法是使用一個弧形路徑走大部分路徑,然後使用closepath命令直線連接到終點)。 - Phrogz
1
Illustrator很烂。你不能用贝塞尔曲线画一个圆。只能画出类似圆的东西,但仍然不是圆。 - adius
@adius 实际上,如果您使用Illustrator创建一个圆并将其保存为SVG,则它会创建一个<circle>元素,即使Illustrator显示的是四段贝塞尔曲线。只有在调整手柄或锚点时,它才会导出为圆形的<path>近似值。 - Phrogz
显示剩余2条评论

4

写成一个函数,它看起来像这样:

function getPath(cx,cy,r){
  return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}

4
这是一个很好的回答!只需小小的补充一下,这条路径从东、北、西、南绘制而成。如果你想让这个圆形的行为就像<circle>标签一样,你必须把方向改为东、南、西、北:"M" + cx + "," + cy + "m" + (r) + ",0" + "a" + r + "," + r + " 0 1,1 " + (-r * 2) + ",0" + "a" + r + "," + r + " 0 1,1 " + (r * 2) + ",0"优点是 stroke-dashoffsetstartOffset 现在可以按相同的方式工作。 - loominade

3

这些答案太过复杂。

有一个更简单的方法可以做到这一点,而不需要创建两个弧形或转换到不同的坐标系统。

假设你的画布区域宽度为w,高度为h

`M${w*0.5 + radius},${h*0.5}
 A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`

只需使用“长弧”标志,使整个旗帜填充。然后将弧线设置为99.9999%的完整圆。在视觉上它是相同的。通过在圆中最右侧点(从中心水平半径)开始绘制圆形来避免扫描标志。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接