你能控制SVG的描边宽度如何绘制吗?

278
目前正在构建一个基于浏览器的SVG应用程序。在这个应用程序中,用户可以对各种形状进行样式和定位,包括矩形。
当我将stroke-width应用于SVG的rect元素时,例如1px,不同的浏览器以不同的方式应用于rect的偏移和插入。这证明是麻烦的,尤其是当我尝试计算矩形的外部宽度和视觉位置,并将其放置在其他元素旁边时。
例如:
- Firefox在底部和左侧添加1px的插入,顶部和右侧添加1px的偏移。 - Chrome在顶部和左侧添加1px的插入,底部和右侧添加1px的偏移。
到目前为止,我唯一的解决方案可能是自己绘制实际的边框(可能使用路径工具),并将边框放在描边元素后面。但这个解决方案是一个不愉快的权宜之计,如果可能的话,我宁愿不走这条路。
所以我的问题是,你能控制SVG的stroke-width如何在元素上绘制吗?

有一些过滤器技巧可以用来实现这个目标,但这并不是一个很好的解决方案。 - Michael Mullany
5
有一个名为 paint-order 的参数,您可以在其中指定填充应该呈现在描边的顶部,这样您就会得到 "外部对齐",请参见 https://jsfiddle.net/hne0kyLg/1/。 - Ivan Kuckir
发现一种使用CSS“outline-”属性的方法:https://codepen.io/badcat/pen/YVzmYY。不确定各个浏览器对此的支持情况,但可能会有用。 - Bernardo Loureiro
SVG 2 还引入了新的 paint-order 属性(Chrome 正在进行 SVG 2 实现,详情请见此处)。 - Mahozad
14个回答

497

不,您无法指定描边是在元素内部还是外部绘制。我在2003年向SVG工作组提出了一个建议,但它没有得到支持(或讨论)。

SVG proposed stroke-location example, from phrogz.net/SVG/stroke-location.svg

正如我在提案中所指出的,

  • 您可以通过将描边宽度加倍,然后使用裁剪路径将对象裁剪到自身来实现与“内部”相同的视觉效果,以及
  • 您可以通过将描边宽度加倍,然后在其上叠加一个无描边的对象副本来实现与“外部”相同的视觉效果。

编辑:这个答案可能是错误的。通过结合veStrokePathveIntersect(用于“内部”)或veExclude(用于“外部”),应该可以使用SVG矢量效果实现这些结果。但是,矢量效果仍然是一个没有我能找到的实现的工作草案模块。

编辑2:SVG 2草案规范包括一个stroke-alignment属性(具有center|inside|outside可能的值)。这个属性可能最终进入UA。

编辑3:令人愉快和失望的是,SVG工作组已经从SVG 2中删除了stroke-alignment。您可以在此处阅读一些描述之后的关注点here


2
我认为这可能是问题所在。为了解决这个问题,我编写了一个名为getStrokedBBox()的包装函数来替代getBBox()。该包装函数根据浏览器对形状描边和偏移的应用返回BBox。它并不完美(需要不断检查最新的浏览器版本),但目前确实准确地提供了形状的外部宽度。 - Steve
7
@Phrogz也许在10年内我们就能看到它了。https://svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint 注解 - frenchone
1
终于来了!我一直在期待这个属性的到来。 - mnsth
1
我创建了一个svg-contour脚本,用于跟踪任何SVGGeometryElement的轮廓,可用作stroke-alignment的解决方法。如果有兴趣,您可以在这里找到描述。 - maioman
3
有没有任何页面可以为这个提案投赞成票?谢谢。看起来很荒谬,竟然不支持。 - mahish
显示剩余4条评论

80

我找到了一种简单的方法,该方法有一些限制,但对我很有效:

  • 在defs中定义形状
  • 定义一个引用该形状的剪辑路径
  • 使用它并将描边加倍,因为外部被剪辑了

这里是一个可行的示例:

<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
 <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
 <clipPath id="clip">
  <use xlink:href="#ld"/>
 </clipPath>
</defs>
<g>
 <use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="#00D2B8" clip-path="url(#clip)"/>
</g>
</svg>


5
我找到的最佳答案 - Eduard Kolosovskyi
2
聪明的解决方案。谢谢。 - Vince Yuan
4
为什么要使用顶级元素<use>?为什么不直接将路径和剪切路径放在其中呢?以下代码同样有效: (是的,这是完全合理且正确的SVG代码,并且可以在所有地方正确地渲染。不要害怕递归。) - Chris Morgan
1
这个是最好的解决方案!!应该标记为解决方案。同时,@ChrisMorgan 谢谢你,你的代码比原来的更简洁,而且同样有效! - Max Gordon
1
保存到独立文件后无法工作。您应该在 <svg ...> 中添加 xmlns:xlink="http://www.w3.org/1999/xlink",或将 xlink:href 替换为 href - Finesse
显示剩余2条评论

78

更新: stroke-alignment属性于2015年4月1日移至全新的SVG Strokes规范中,详情请点击此处

自2015年2月26日的SVG 2.0编辑草案(以及可能自2月13日起),stroke-alignment属性存在,其取值为innercenter (默认)outer

它似乎与@Phrogz提出的stroke-location属性和后来的stroke-position建议以相同的方式工作。这个属性自2011年就已计划,但除了注释说

SVG 2应包括一种指定描边位置的方法

除了这个之外,在规范中从未详细说明过,因为它被推迟了-直到现在看来。

没有浏览器支持此属性,或者,据我所知,任何新的SVG 2功能,但希望随着规范的成熟,它们很快就会有支持。这一直是我个人迫切需要的一个属性,我真的很高兴它终于出现在规范中了。

似乎存在一些关于该属性如何在打开的路径以及环上运作的问题。这些问题可能会延长浏览器的实现时间。然而,当浏览器开始支持此属性时,我将使用新信息更新此答案。


1
stroke-alignment 在 SVG Strokes 中有定义,这是 W3C Working Draft。同时,SVG 2 W3C Editor's Draft 表示应该在 https://svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint 的 SVG 规范中增加一个描边定位属性,但该规范已经达到了 W3C Candidate Recommendation 状态,除了一个指向 stroke-position 提案的链接之外,并没有这样的属性存在于规范中,这似乎不太可能实现。 - Patrick Dark

67

1
这太完美了!谢谢分享。 - brubs
1
链接的文档似乎没有说明笔画是在内部、外部还是居中?您能否澄清如何控制笔画的绘制位置?谢谢。 - Crashalot
7
描边仍然是居中的,就像以前一样——但如果你有一个实心填充,将绘画顺序调整为先描边,则内部的一半描边会被覆盖,这与在外部绘制一条厚度减半的描边具有相同的效果。 - Chris Morgan
2
只要填充是实心的,这是一个很好的解决方法。如果它是透明的,则轮廓的内半部分变得可见,就像标准顺序一样。 - Tad Lispy
1
我怎样才能使它内部描边?它现在是外部描边。 - James Urian

8
这里是一个函数,可以根据浏览器计算需要添加多少像素值到顶部、右侧、底部和左侧,这些像素值基于给定的描边大小。
var getStrokeOffsets = function(stroke){

        var strokeFloor =       Math.floor(stroke / 2),                                                                 // max offset
            strokeCeil =        Math.ceil(stroke / 2);                                                                  // min offset

        if($.browser.mozilla){                                                                                          // Mozilla offsets

            return {
                bottom:     strokeFloor,
                left:       strokeFloor,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }else if($.browser.webkit){                                                                                     // WebKit offsets

            return {
                bottom:     strokeCeil,
                left:       strokeFloor,
                top:        strokeFloor,
                right:      strokeCeil
            };

        }else{                                                                                                          // default offsets

            return {
                bottom:     strokeCeil,
                left:       strokeCeil,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }

    };

6
正如其他人所指出的那样,您需要重新计算描边路径坐标的偏移量或将其宽度加倍,然后遮罩其中一侧,因为SVG不仅不支持Illustrator的描边对齐,PostScript也不支持。 Adobe的PostScript手册第二版中关于描边的规范声明:“4.5.1 描边:‘描边’操作符沿着当前路径绘制一条某种厚度的线。对于路径中的每个直线或曲线段,‘描边’都会绘制一条线,该线以段落为中心,两侧平行。”(强调为他们所提供)。规范的其余部分没有用于偏移线条位置的属性。当Illustrator允许您对齐内部或外部时,它会重新计算实际路径的偏移量(因为这仍然比过印和遮罩更便宜)。 .ai文档中的路径坐标是参考值,而不是最终呈现或导出到最终格式的内容。由于Inkscape的本地格式是规范的SVG格式,因此无法提供规范缺乏的功能。

4

这里有一个使用symboluse的解决方案,来实现内边框rect

示例: https://jsbin.com/yopemiwame/edit?html,output

SVG:

<svg>
  <symbol id="inner-border-rect">
    <rect class="inner-border" width="100%" height="100%" style="fill:rgb(0,255,255);stroke-width:10;stroke:rgb(0,0,0)">
  </symbol>
  ...
  <use xlink:href="#inner-border-rect" x="?" y="?" width="?" height="?">
</svg>

注意:请确保用实际值替换use中的?
背景:这个技术之所以有效,是因为symbol通过将symbol替换为svg并在影子DOM中创建一个元素来建立新的视口。然后将该影子DOM中的svg链接到当前的SVG元素中。请注意,svg可以嵌套,并且每个svg都会创建一个新的视口,它会裁剪所有重叠的内容,包括重叠的边框。有关更详细的概述,请阅读Sara Soueidan的这篇精彩文章

2

我不知道这是否有用,但在我的情况下,我只是创建了另一个只有边框的圆形,并将其放置在另一个形状的“内部”。


1
一个(不太优雅的)可能解决方案是使用模式,以下是一个内部带有描边三角形的示例:

https://jsfiddle.net/qr3p7php/5/

<style>
#triangle1{
  fill: #0F0;
  fill-opacity: 0.3;
  stroke: #000;
  stroke-opacity: 0.5;
  stroke-width: 20;
}
#triangle2{
  stroke: #f00;
  stroke-opacity: 1;
  stroke-width: 1;
}    
</style>

<svg height="210" width="400" >
    <pattern id="fagl" patternUnits="objectBoundingBox" width="2" height="1" x="-50%">
        <path id="triangle1" d="M150 0 L75 200 L225 200 Z">
    </pattern>    
    <path id="triangle2" d="M150 0 L75 200 L225 200 Z" fill="url(#fagl)"/>
</svg>

1

我发现最简单的方法是将clip-path添加到圆形中

添加clip-path="circle()"

<circle id="circle" clip-path="circle()" cx="100" cy="100" r="100" fill="none" stroke="currentColor" stroke-width="5" />

然后stroke-width="5"就会神奇地变成内部5像素宽度,具有绝对100像素半径。


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