Search code examples
csssvgvector-graphicsstroke

SVG "vector-effect: non-scaling-stroke" in <marker>: Firefox vs Chrome


Consider the following snippet, which contains two versions of an SVG with an arrowhead defined by a <marker>. Lines and paths are styled with vector-effect: non-scaling-stroke. The second version is simply the first version with all coordinates divided by 10 (and the marker id changed to something else).

JSFiddle: https://jsfiddle.net/nkevo5ja

On Windows 10:

  • Firefox 78.0.2 (64-bit): Arrowheads are the same size.
  • Chrome 84.0.4147.89 (64-bit): Arrowheads are different sizes.
    If vector-effect: non-scaling-stroke is removed, the arrowheads become the same size (but this breaks the line-width).

Whose implementation is incorrect?

Firefox: same arrowhead size; Chrome: different arrowhead size.

<!-- Styles -->
<svg width="0" height="0">
  <style>
    line, path {
      stroke: black;
      vector-effect: non-scaling-stroke;
    }
  </style>
</svg>

<!-- Larger version -->
<svg width="120px" height="100%" viewBox="0 0 400 300">
  <defs>
    <marker id="larger"
      viewBox="0 -20 70 40"
      refX="70" refY="0"
      markerWidth="70" markerHeight="40"
      orient="auto"
    >
      <path d="M 70 0 L 0 -20 L 0 20 z"/>
    </marker>
  </defs>
  <line x1="400" y1="300" x2="0" y2="0" marker-end="url(#larger)"/>
</svg>

<!-- Smaller version (larger version with all coordinates divided by 10) -->
<svg width="120px" height="100%" viewBox="0 0 40 30">
  <defs>
    <marker id="smaller"
      viewBox="0 -2 7 4"
      refX="7" refY="0"
      markerWidth="7" markerHeight="4"
      orient="auto"
    >
      <path d="M 7 0 L 0 -2 L 0 2 z"/>
    </marker>
  </defs>
  <line x1="40" y1="30" x2="0" y2="0" marker-end="url(#smaller)"/>
</svg>

Chromium issue: https://crbug.com/1107791


Solution

  • Firefox has the correct behavior, there is even a note in the specs about this exact case:

    When ‘markerUnits’ has the value strokeWidth, the size of the marker is relative to the stroke-width after it has had any transforms applied that affect the width of the stroke in the user coordinate system for the stroke. This means that, for example, the vector-effect attribute with a value of non-scaling-stroke will result in the markers also being non scaling.

    strokeWidth being the default value for markerUnits.


    Edit: Turns out my reading of the specs was wrong and that what should happen is the markerWidth and markerHeight are multiplied by the final computed strokewidth of the stroked line – 1px here – just like Chrome did.

    You can refer to Robert Longson's answer, who did push a fix for Firefox so it aligns with Chrome and the specs.


    Using this exact snippet, you can get the same results in both Chrome and Firefox by setting this markerUnits attribute to userSpaceOnUse.

    <!-- Styles -->
    <svg width="0" height="0">
      <style>
        line, path {
          stroke: black;
          vector-effect: non-scaling-stroke;
        }
      </style>
    </svg>
    
    <!-- Larger version -->
    <svg width="120px" height="100%" viewBox="0 0 400 300">
      <defs>
        <marker id="larger"
          viewBox="0 -20 70 40"
          refX="70" refY="0"
          markerUnits="userSpaceOnUse"
          markerWidth="70" markerHeight="40"
          orient="auto"
        >
          <path d="M 70 0 L 0 -20 L 0 20 z"/>
        </marker>
      </defs>
      <line x1="400" y1="300" x2="0" y2="0" marker-end="url(#larger)"/>
    </svg>
    
    <!-- Smaller version (larger version with all coordinates divided by 10) -->
    <svg width="120px" height="100%" viewBox="0 0 40 30">
      <defs>
        <marker id="smaller"
          viewBox="0 -2 7 4"
          refX="7" refY="0"
          markerUnits="userSpaceOnUse"
          markerWidth="7" markerHeight="4"
          orient="auto"
        >
          <path d="M 7 0 L 0 -2 L 0 2 z"/>
        </marker>
      </defs>
      <line x1="40" y1="30" x2="0" y2="0" marker-end="url(#smaller)"/>
    </svg>