你能控制如何绘制SVG's stroke-width吗?

目前正在构建一个基于浏览器的SVG应用程序。在这个应用程序中,用户可以设置各种形状的样式和位置,包括矩形。

当我将stroke-width应用到SVG的rect元素(例如1px)时,笔画会被不同的浏览器以不同的方式应用到rect的偏移和插入。这被证明是麻烦的,特别是当我试图计算一个矩形的外部宽度和视觉位置,并将其定位在其他元素旁边。

例如:

  • Firefox添加了1px的插入(底部和左侧)和1px的偏移(顶部和右侧)
  • Chrome添加了1px的插入(顶部和左侧),和1px的偏移(底部和右侧)

到目前为止,我唯一的解决方案是自己绘制实际的边界(可能使用path工具),并将边界定位在笔画元素的后面。但这种解决方案是一种令人不快的变通方法,如果可能的话,我宁愿不走这条路。

所以我的问题是,你能控制SVG的stroke-width是如何在元素上绘制的吗?

157315 次浏览

不能,不能指定笔画在元素内部还是外部。我在2003年为SVG工作组做了一个提议来实现这个功能,但它没有得到任何支持(或讨论)。

SVG提议的笔画位置示例,from phrogz.net/SVG/stroke-location.svg

正如我在提案中提到的,

  • 你可以通过加倍描边宽度来实现与“inside”相同的视觉效果,然后使用剪辑路径将对象剪辑到自身
  • 你可以通过将描边宽度加倍,然后将对象的无描边副本叠加在其上,从而获得与“外部”相同的视觉效果。

这个答案将来可能是错误的。应该可以使用SVG矢量效果实现这些结果,方法是将veStrokePathveIntersect(用于“内部”)或veExclude(用于“外部”)组合在一起。然而,矢量效果仍然是一个工作草案模块,没有实现,我还能找到。

编辑2: SVG 2规范草案包含一个stroke-alignment属性(中间|在可能值的外面|)。这个特性可能最终会被应用到无人机中。

编辑3:有趣又令人失望的是,SVG工作组已经从SVG 2中删除了stroke-alignment。你可以看到散文在这里之后描述的一些关注点。

下面是一个函数,它将计算你需要添加多少像素-使用给定的笔画-到顶部,右侧,底部和左侧,所有这些都基于浏览器:

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
};


}


};

如上所述,你必须重新计算笔画路径坐标的偏移量,或者将其宽度翻倍,然后掩码一侧或另一侧,因为不仅SVG本身不支持Illustrator的笔画对齐,而且PostScript也不支持。

Adobe's PostScript Manual第二版中的笔画规范规定: “4.5.1抚摸: 中风操作符沿着当前路径绘制一条一定粗细的线。对于路径中的每个直线或曲线段,中风在该段上绘制一条为中心的的直线,边长为平行。”(并强调了这一点)< / p >

规范的其余部分没有用于偏移行的位置的属性。当Illustrator让你在内部或外部对齐时,它会重新计算实际路径的偏移量(因为它仍然比套印然后掩蔽计算更便宜)。.ai文档中的路径坐标是引用,而不是被光栅化或导出为最终格式的内容。

因为Inkscape的原生格式是规范SVG,所以它不能提供规范所缺乏的特性。

更新:属性stroke-alignment

截至2015年2月26日SVG 2.0编辑稿(可能从2月13日开始), stroke-alignment属性存在的值为innercenter(默认)outer

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

SVG 2应该包括一种指定笔画位置的方法

,它从未在规范中详细说明,因为它是递延 -直到现在,似乎是这样。

据我所知,目前还没有浏览器支持这个属性,或者SVG 2的任何新特性,但希望随着规范的成熟,它们很快就会支持。这是我个人一直敦促拥有的特性,我真的很高兴它最终出现在规范中。

似乎有一些问题,关于属性应该如何在开放路径和循环上表现。这些问题很可能会延长跨浏览器的实现。但是,随着浏览器开始支持这个属性,我将用新的信息更新这个答案。

一个(肮脏的)可能的解决方案是使用模式,

下面是一个内描边三角形的例子:

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>

下面是使用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通过用svg替换symbol并在shadow DOM中创建一个元素来建立一个新的视口。这个影子DOM的svg随后被链接到你当前的SVG元素。注意,svg可以嵌套,并且每个svg都会创建一个新的视口,它会剪辑所有重叠的部分,包括重叠的边界。对于正在发生的事情的更详细的概述,阅读Sara Soueidan的这篇精彩的文章

我发现了一个简单的方法,虽然有一些限制,但对我来说很有效:

  • 在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>

我不知道这有多大帮助,但在我的情况下,我只是创建了另一个圆,只有边界,并将其“在”另一个形状。

您可以使用CSS样式的笔画和填充顺序。即先描边,后填充,得到想要的效果。

MDN在paint-order: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order

CSS代码:

paint-order: stroke;

泽维尔何的解决方案是将笔画的宽度增加一倍,并改变油漆顺序,这是非常棒的,尽管只有当填充是纯色时才有效,没有透明度。

我已经开发了其他方法,更复杂,但适用于任何填充。它也适用于椭圆或路径(后者存在一些具有奇怪行为的角落情况,例如交叉自己的开放路径,但并不多)。

诀窍是在两层中显示形状。一个没有笔画(只有填充),另一个只有两倍宽度的笔画(透明填充),并通过一个遮罩,显示整个形状,但隐藏了没有笔画的原始形状。

  <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"/>
<mask id="mask">
<use xlink:href="#ld" stroke="#FFFFFF" stroke-width="160" fill="#FFFFFF"/>
<use xlink:href="#ld" fill="#000000"/>
</mask>
</defs>
<g>
<use xlink:href="#ld" fill="#00D2B8"/>
<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="red" mask="url(#mask)"/>
</g>
</svg>

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

添加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"将神奇地变成内5px笔画,绝对半径100px。