目前正在构建一个基于浏览器的 SVG 应用程序。在这个应用程序中,用户可以设置各种形状的样式和位置,包括矩形。
当我将 stroke-width
应用于 1px
的 SVG rect
元素时,不同浏览器以不同方式将笔划应用于 rect
的偏移和插入。事实证明这很麻烦,尤其是,当我尝试计算矩形的外部宽度和视觉位置并将其放置在其他元素旁边时。
例如:
Firefox 添加 1px 插入(底部和左侧)和 1px 偏移(顶部和右侧)
Chrome 添加 1px 插入(顶部和左侧)和 1px 偏移(底部和右侧)
到目前为止,我唯一的解决方案是自己绘制实际边框(可能使用 path
工具)并将边框放置在描边元素的后面。但是这个解决方案是一个令人不快的解决方法,如果可能的话,我宁愿不要走这条路。
所以我的问题是,你能控制 SVG 的 stroke-width
是如何绘制在元素上的吗?
paint-order
参数,您可以在其中指定填充应呈现在笔画之上,因此您将获得“外部对齐”,请参阅 jsfiddle.net/hne0kyLg/1
paint-order
property(SVG 2 实现似乎是 Chrome 中的 in progress)。
不,您不能指定笔画是在元素内部还是外部绘制。我在 2003 年向 SVG 工作组提交了此功能的a proposal,但没有得到任何支持(或讨论)。
https://i.stack.imgur.com/I0KEg.png
正如我在提案中指出的那样,
您可以通过将笔画宽度加倍然后使用剪切路径将对象剪切到自身来实现与“内部”相同的视觉效果,并且
您可以通过将笔划宽度加倍然后在其自身顶部覆盖对象的无笔划副本来实现与“外部”相同的视觉效果。
编辑:这个答案将来可能是错误的。通过将 veStrokePath
与 veIntersect
(用于“内部”)或与 veExclude
(用于“外部”)结合使用 SVG Vector Effects,应该可以实现这些结果。但是,矢量效果仍然是一个工作草案模块,我还没有找到任何实现。
编辑 2:SVG 2 草案规范包括一个 stroke-alignment
属性(具有 center|inside|outside 可能的值)。该属性最终可能会成为 UA。
编辑 3:有趣且令人失望的是,SVG 工作组已将 stroke-alignment
从 SVG 2 中删除。您可以看到散文 here 之后描述的一些问题。
更新: stroke-alignment
属性 was on April 1st, 2015 moved to a completely new spec called SVG Strokes。
从 2015 年 2 月 26 日的 SVG 2.0 编辑草稿开始(可能从 February 13th 开始),the 的值为 stroke-alignment
property is presentinner
、center
(默认) 和 outer
。
它的工作方式似乎与@Phrogz 和后来的 stroke-position
suggestion 提出的 stroke-location
属性相同。这个属性至少从 2011 年就已经计划好了,但是除了一个注释说
SVG 2 应包括一种指定笔画位置的方法
,它从未在规范中详细说明,因为它被推迟了——似乎直到现在。
目前还没有浏览器支持这个属性,或者,据我所知,任何新的 SVG 2 特性,但希望它们会在规范成熟后尽快实现。这是我个人一直敦促拥有的属性,我真的很高兴它终于出现在规范中。
关于属性如何在开放路径和循环上表现似乎存在一些问题。这些问题很可能会延长跨浏览器的实现。但是,随着浏览器开始支持此属性,我将使用新信息更新此答案。
stroke-alignment
在 W3C 工作草案 SVG Strokes 中指定。同时,SVG 2 W3C Editor's Draft 说笔画定位属性应在 svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint 的 SVG 规范中,但该规范已达到 W3C 候选推荐状态,除了指向 { 3} 提案,看起来情况并非如此。
我找到了一种简单的方法,它有一些限制,但对我有用:
在 defs 中定义形状
定义引用形状的剪辑路径
使用它并将笔画加倍,因为外部被剪裁
这是一个工作示例:
<use>
?为什么不直接把路径和剪辑路径放进去呢?这工作得很好:<svg width="240" height="240" viewBox="0 0 1024 1024"> <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z" clip-path="url(#clip)" stroke="#0081C6" stroke-width="160" fill="#00d2b8"/> <clipPath id="clip"> <use xlink:href="#ld"/> </clipPath> </svg>
。 (是的,这是完全合理且正确的 SVG,并且会在任何地方正确渲染。不要担心递归。)
您可以使用 CSS 来设置笔触和填充的顺序。即先描边,后填充,得到想要的效果。
paint-order
上的 MDN:https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order
CSS 代码:
paint-order: stroke;
这是一个函数,它将计算您需要添加多少像素 - 使用给定的笔画 - 到顶部、右侧、底部和左侧,所有这些都基于浏览器:
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 的 PostScript 手册第 2 版中的笔划规范指出:“4.5.1 笔划:笔划操作符沿着当前路径绘制一条粗细的线。对于路径中的每个直线或曲线段,笔划绘制一条以边平行于线段的线段。" (强调他们的)
规范的其余部分没有用于偏移线位置的属性。当 Illustrator 让您在内部或外部对齐时,它会重新计算实际路径的偏移量(因为它在计算上仍然比叠印然后蒙版便宜)。 .ai 文档中的路径坐标是参考,而不是被光栅化或导出为最终格式的坐标。
因为 Inkscape 的原生格式是规范 SVG,所以它不能提供规范所缺乏的功能。
以下是使用 symbol
和 use
解决 innerbordered 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
并在 shadow DOM 中创建一个元素来建立一个新的视口。然后将这个 svg
的影子 DOM 链接到您当前的 SVG
元素。请注意,svg
可以嵌套,并且每个 svg
都会创建一个新视口,该视口会剪切所有重叠的内容,包括重叠的边框。有关正在发生的事情的更详细的概述,请阅读 Sara Soueidan 的 this fantastic article。
我不知道这会有多大帮助,但在我的例子中,我只是创建了另一个只有边框的圆圈,并将它放在另一个形状的“内部”。
一个(肮脏的)可能的解决方案是使用模式,
这是一个内部描边三角形的示例:
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>
Xavier Ho 中将笔画宽度加倍并更改绘制顺序的解决方案非常出色,尽管仅在填充为纯色且没有透明度时才有效。
我开发了其他方法,更复杂但适用于任何填充。它也适用于椭圆或路径(后者有一些具有奇怪行为的极端情况,例如交叉的开放路径,但不多)。
诀窍是在两层中显示形状。一个没有描边(仅填充),另一个只有双倍宽度的描边(透明填充)并通过显示整个形状的蒙版,但隐藏了没有描边的原始形状。
<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()"
<circle id="circle" clip-path="circle()" cx="100" cy="100" r="100" fill="none" stroke="currentColor" stroke-width="5" />
然后 stroke-width="5"
将神奇地变成绝对半径为 100px 的内部 5px 笔划。