htmlcsssvgstroke

How can I make a SVG stroke show a background?


I have an SVG with a gradient-like background and a foreground icon. Currently, the icon is just one filled path with polylines on top, but it can be instead split into multiple paths where the polyline would be the stroke. However, I want to, instead of the polyline/stroke having a colour, have them display the background, as though it has cut a hole in the foreground. Due to the background being a gradient (it's actually a shadow filter) and not a solid colour, I can't just have a stroke with the same colour as the background to do this. This is what I imagine the result would be if I could have a negative stroke-width on the multiple paths.

As an example, I'll use the SVG this is actually for (my profile picture) So, this is my current SVG code

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" stroke="black" fill="rgb(95,95,95)" viewBox="-30 -30 276 260" width="276" height="260" stroke-width="4">
  <defs>
    <filter id="shadow" x="-30" y="-30" width="276" height="260">
      <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32"/>
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
    </filter>
  </defs>

  <rect x="-30" y="-30" width="276" height="260" fill="#c41313" stroke="none"/>
  <path d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" filter="url(#shadow)"/>
  <g fill="none">
    <polyline points="82 166, 108 179, 135 166"/>
    <polyline points="54 130, 108 157, 162 130"/>
    <polyline points="0 54, 108 108, 216 54"/>
    <polyline points="49 78.5, 108 49, 167 78.5"/>
    <line x1="108" y1="0" x2="108" y2="49"/>
    <line x1="108" y1="200" x2="108" y2="108"/>
  </g>
</svg>
I would like, instead of the black outline, it to be a gap in the icon, like this. Of course, for this particular image, I could just manually make each section- separated by the black outlines- have it's own path without a stroke, re-creating the effect, but due to how I am using this image, I need the stroke-width to be variable, and having specific paths would not allow for this, unless there were a way to automatically generate these paths in Javascript or PHP but I wouldn't know how to implement this. The only way I can think of getting around this would be to find the centres of each section and increase a grey outline to decrease the gap (so a maximum gap would be a stroke-width of 0 and a minimum gap would have a stroke-width equivalent to the radius of each section), but for my situation this would still not be effective. So what alternatives are there?


Solution

  • A more conventional approach would be to use a <mask>:

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276 260" width="276" height="260">
      <defs>
        <mask id="mask" stroke-width="4" stroke="#000">
          <g transform="translate(30 30)">
            <path id="shapeBG" d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" fill="#fff" />
            <g fill="none">
              <polyline points="82 166, 108 179, 135 166" />
              <polyline points="54 130, 108 157, 162 130" />
              <polyline points="0 54, 108 108, 216 54" />
              <polyline points="49 78.5, 108 49, 167 78.5" />
              <line x1="108" y1="0" x2="108" y2="49" />
              <line x1="108" y1="200" x2="108" y2="108" />
            </g>
          </g>
        </mask>
        <filter id="shadow" x="0" y="0" width="276" height="260">
          <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32" />
          <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
      </defs>
      <rect id="bg" x="0" y="0" width="100%" height="100%" fill="#c41313" stroke="none" />
      <g filter="url(#shadow)">
        <rect x="0" y="0" width="100%" height="100%" mask="url(#mask)" fill="rgb(95,95,95)" />
      </g>
    </svg>
    
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="-30 -30 276 260" width="276" height="260">
      <g id="mask" stroke-width="4" stroke="#000">
        <path id="shapeBG" d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" fill="#fff" />
        <g fill="none">
          <polyline points="82 166, 108 179, 135 166" />
          <polyline points="54 130, 108 157, 162 130" />
          <polyline points="0 54, 108 108, 216 54" />
          <polyline points="49 78.5, 108 49, 167 78.5" />
          <line x1="108" y1="0" x2="108" y2="49" />
          <line x1="108" y1="200" x2="108" y2="108" />
        </g>
        <text style="font-size:11; font-family:Arial, sans-serif" stroke-width="0" x="0" y="80%" dominant-baseline="middle">Mask: white fills/strokes=opaque; <tspan x="0" dy="12">black fills/strokes=transparent<tspan></text>
    </svg>

    On the right you see the actual mask graphic:

    <mask> could also be used to get semi-transparent strokes e.g by setting your stroke color to something like rgb(128,128,128).

    Regarding software support you might also realign your graphics via a transform to avoid negative viewBox values: some editors have issues with these.