【问题标题】:Can you control how an SVG's stroke-width is drawn?你能控制如何绘制 SVG 的笔画宽度吗?
【发布时间】:2011-11-06 16:05:12
【问题描述】:

目前正在构建一个基于浏览器的 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 offset rect


【解决方案1】:

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

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

  • 您可以通过将笔画宽度加倍然后使用剪切路径将对象剪切到自身来获得与“内部”相同的视觉效果,并且
  • 您可以通过将笔划宽度加倍然后在其自身之上覆盖对象的无笔划副本来获得与“外部”相同的视觉效果。

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

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

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

【讨论】:

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

更新: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 候选推荐状态,除了指向 @ 的链接之外,规范中没有此类属性987654334@ 提案,看起来情况并非如此。
【解决方案3】:

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

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

【讨论】:

  • 我找到的最佳答案
  • 聪明的解决方案。谢谢
  • 这应该被接受为答案。使用 是目前可用的最优雅的解决方案。
  • 不错的解决方案。您将如何反转它以进行外部中风?
  • 为什么是顶级&lt;use&gt;?为什么不直接把路径和剪辑路径放进去呢?这很好用:&lt;svg width="240" height="240" viewBox="0 0 1024 1024"&gt; &lt;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"/&gt; &lt;clipPath id="clip"&gt; &lt;use xlink:href="#ld"/&gt; &lt;/clipPath&gt; &lt;/svg&gt;。 (是的,这是完全合理且正确的 SVG,并且会在任何地方正确渲染。不要担心递归。)
【解决方案4】:

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

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

CSS 代码:

paint-order: stroke;

【讨论】:

  • 这太完美了!感谢分享
  • 链接的文档似乎没有指出笔划是在内部、外部还是居中?您能否阐明如何控制笔画的绘制位置?谢谢。
  • 笔划居中,就像往常一样——但如果你有一个实心填充,调整绘制顺序以首先绘制笔划的效果是笔划的内半部分被绘制,这与在外面画半粗笔画的效果相同。
  • 这是一个很好的解决方法,只要填充是实心的。如果它是透明的,则笔画的内半部分变得可见,就像标准顺序一样。
  • 我怎样才能让它在里面描边?它在外面中风了。
【解决方案5】:

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

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 手册第 2 版中的笔划规范指出: "4.5.1 抚摸: stroke 操作符沿着当前路径绘制一条粗细的线。对于路径中的每个直线段或曲线段,stroke 会在该段上居中 绘制一条线,边平行 到该段。" (强调他们的)

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

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

    【讨论】:

      【解决方案7】:

      这里是 innerbordered rect 使用 symboluse 的解决方法。

      示例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 并在shadow DOM 中创建一个元素来建立一个新的视口。这个影子 DOM 的 svg 然后链接到您当前的 SVG 元素。请注意,svgs 可以嵌套,并且每个svg 都会创建一个新视口,该视口会剪切所有重叠的内容,包括重叠的边框。有关正在发生的事情的更详细概述,请阅读 Sara Soueidan 的 this fantastic article

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          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>
          

          【讨论】:

            【解决方案10】:

            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>
            

            【讨论】:

              【解决方案11】:

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

              添加clip-path="circle()"

              &lt;circle id="circle" clip-path="circle()" cx="100" cy="100" r="100" fill="none" stroke="currentColor" stroke-width="5" /&gt;

              然后stroke-width="5" 将神奇地变成绝对半径为 100px 的内部 5px 笔划。

              【讨论】:

                猜你喜欢
                • 2013-07-09
                • 2013-03-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-05-20
                • 2010-11-21
                • 2016-05-02
                • 2011-01-24
                相关资源
                最近更新 更多