ChatGPT解决这个技术问题 Extra ChatGPT

你能控制如何绘制 SVG 的笔画宽度吗?

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

当我将 stroke-width 应用于 1px 的 SVG rect 元素时,不同浏览器以不同方式将笔划应用于 rect 的偏移和插入。事实证明这很麻烦,尤其是,当我尝试计算矩形的外部宽度和视觉位置并将其放置在其他元素旁边时。

例如:

Firefox 添加 1px 插入(底部和左侧)和 1px 偏移(顶部和右侧)

Chrome 添加 1px 插入(顶部和左侧)和 1px 偏移(底部和右侧)

到目前为止,我唯一的解决方案是自己绘制实际边框(可能使用 path 工具)并将边框放置在描边元素的后面。但是这个解决方案是一个令人不快的解决方法,如果可能的话,我宁愿不要走这条路。

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

您可以使用过滤器技巧来实现这一点 - 但这不是一个很好的解决方案
paint-order 参数,您可以在其中指定填充应呈现在笔画之上,因此您将获得“外部对齐”,请参阅 jsfiddle.net/hne0kyLg/1
找到了一种使用 css 'outline-' 属性的方法:codepen.io/badcat/pen/YVzmYY。不确定跨浏览器对此的支持是什么,但可能有用。
SVG 2 还引入了新的 paint-order property(SVG 2 实现似乎是 Chrome 中的 in progress)。

P
Phrogz

不,您不能指定笔画是在元素内部还是外部绘制。我在 2003 年向 SVG 工作组提交了此功能的a proposal,但没有得到任何支持(或讨论)。

https://i.stack.imgur.com/I0KEg.png

正如我在提案中指出的那样,

您可以通过将笔画宽度加倍然后使用剪切路径将对象剪切到自身来实现与“内部”相同的视觉效果,并且

您可以通过将笔划宽度加倍然后在其自身顶部覆盖对象的无笔划副本来实现与“外部”相同的视觉效果。

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

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

编辑 3:有趣且令人失望的是,SVG 工作组已将 stroke-alignment 从 SVG 2 中删除。您可以看到散文 here 之后描述的一些问题。


以为可能是这样。为了解决这个问题,我为 getBBox() 编写了一个名为 getStrokedBBox() 的包装函数。此包装器根据浏览器如何将笔划应用于形状的插入和偏移来返回 BBox。它并不完美(需要不断检查最新的浏览器版本),但它现在确实准确地提供了形状的外部宽度。
@Phrogz 也许我们会在 10 年过去之前看到它。 svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint 注释
It's finally here! 一方面,我确实一直在敦促此房产到货。
我创建了一个 svg-contour script 用于跟踪任何 SVGGeometryElement 的轮廓,它可以用作笔画对齐的解决方法,任何有兴趣的人都可以找到描述 here
是否有任何页面可以让我为提案投票?谢谢。似乎很荒谬,它不受支持。
g
gilly3

更新: 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 present 的值为 innercenter (默认)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} 提案,看起来情况并非如此。
B
Bharata

我找到了一种简单的方法,它有一些限制,但对我有用:

在 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,并且会在任何地方正确渲染。不要担心递归。)
X
Xavier Ho

您可以使用 CSS 来设置笔触和填充的顺序。即先描边,后填充,得到想要的效果。

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

CSS 代码:

paint-order: stroke;

太棒了!感谢分享
链接的文档似乎没有表明笔划是在内部、外部还是居中?您能否阐明如何控制笔画的绘制位置?谢谢。
笔画是居中的,就像往常一样——但是如果你有一个实心填充,那么将绘制顺序调整为首先绘制笔画的效果是笔画的内半部分被绘制,这与绘制一个外面有一半粗的中风。
只要填充牢固,这是一个很好的解决方法。如果它是透明的,则笔画的内半部分变得可见,就像标准顺序一样。
我怎样才能让它在里面抚摸?它在外面中风了。
S
Steve

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

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

        }

    };

u
user1574945

正如上面的人所指出的,您要么必须重新计算笔划路径坐标的偏移量,要么将其宽度加倍,然后遮盖一侧或另一侧,因为 SVG 不仅不支持 Illustrator 的笔划对齐,而且 PostScript 也不支持.

Adobe 的 PostScript 手册第 2 版中的笔划规范指出:“4.5.1 笔划:笔划操作符沿着当前路径绘制一条粗细的线。对于路径中的每个直线或曲线段,笔划绘制一条以边平行于线段的线段。" (强调他们的)

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

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


F
F Lekschas

以下是使用 symboluse 解决 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


T
The Witness

我不知道这会有多大帮助,但在我的例子中,我只是创建了另一个只有边框的圆圈,并将它放在另一个形状的“内部”。


m
max-lt

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

这是一个内部描边三角形的示例:

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>

h
hirunatan

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>

Y
Yoki Yu

我发现最简单的方法是将剪辑路径添加到圆圈中

添加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 笔划。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅