Home > Mobile >  SVG filter that fills area between two polylines
SVG filter that fills area between two polylines

Time:12-06

i am trying to learn how SVG filters work and came up with the following situation:

There is an input SVG like this, minimal example:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
  <polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>

Now my quest is to create a filter that does not need to change the two polylines but adds color to the SVG between the two polylines.

My naive idea is:

1.) replicate the outer object and fill its inside space with some color.

2.) replicate the inner object the same way and fill it with the same color

3.) add the difference of both to the image (with a Z-position below the original polylines so they stay visible)

My first attempt looks like this:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="fillingFilter">
      <use href="#inner" style="fill:green" />
      <feComposite in2="sourceGraphic" operator="out" />
    </filter>
  </defs>
  <polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
  <polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
  <use href="#outer" filter="url(#fillingFilter)" style="fill:green" />
</svg>

This does not work, as the added fillings are ignored, and this way the composite seems to do nothing. From studying the SVG documentation i can't really find out how to do it.

What is the right way to achieve the result?

CodePudding user response:

You don't really need a filter for this - just convert your polylines to a compound path.

  • Concatenate both polyline points attributes:

  • Prepend a M (Moveto - starting point) command and append a z command (closepath)

  • copy these values to a <path> <d> attribute like so:

    <path d="M 50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0 z M 35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0 z" fill-rule="evenodd" />

  • add a fill-rule="evenodd" – this way inner shapes will be subtracted from the outer shape.

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
  <polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path id="outer" 
        d="
          M 50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0z
          M 35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0z" 
        fill-rule="evenodd" 
        stroke="black" 
        stroke-width="1" 
        fill="green" />
</svg>

Combine polylines via javaScript helper

You can easily automate this process by writing a simple helper method.

let svg = document.querySelector('svg');
polysToPath(svg)

function polysToPath(svg){
  let polylines = document.querySelectorAll('polyline');
  // create compound path
  let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  svg.insertBefore(path, polylines[0]);
  
  // combinded path data
  let d = '';
  polylines.forEach(poly=>{
    d  = 'M' poly.getAttribute('points') 'z';
    //remove polyline
    poly.remove();
  }) 
  
  path.setAttribute('d', d);
  // style new path via css class or attributes
  path.classList.add('polyPath');
  //path.setAttribute('fill-rule', 'evenodd');
  //path.setAttribute('fill', 'green');
}
svg{
  width:20em
}

.polyPath{
  fill-rule: evenodd;
  fill:green;
}
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <polyline id="outer" points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0" stroke="black" stroke-width="1" fill="none" />
  <polyline id="inner" points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0" stroke="black" stroke-width="1" fill="none" />
</svg>

CodePudding user response:

Points are for herrstrietzel.

You can shorten his code with a native Web Component, supported in all modern browsers:

<svg-merge-polylines>
  <polyline points="50.0,10.0 90.0,90.0 10.0,90.0 50.0,10.0"/>
  <polyline points="35.0,50.0 65.0,50.0 50.0,80.0 35.0,50.0"/>
</svg-merge-polylines>

<script>
customElements.define("svg-merge-polylines", class extends HTMLElement {
  connectedCallback() {
    setTimeout(() => { // wait till innerHTML/lightDOM is parsed
      let d = [...document.querySelectorAll('polyline')]
                .map(p => 'M'   p.getAttribute('points')   'z');
      this.innerHTML = `<svg viewBox="0 0 100 100" style="height:180px">`  
        `<path d="${d.join("")}" fill-rule="evenodd" fill="green"/>`  
        `</svg>`;
    });
  }
})
</script>

  • Related