Search code examples
csssvgsvg-filters

CSS reference to SVG filter in separate element


Playing around with SVG filters, I ran into an issue with referencing an SVG filter from CSS. In some situations, applying a filter will remove the element to which the filter is applied from the page. I thought that there could be 2 causes:

  1. the fact that I was adding the filters dynamically using D3JS;
  2. the fact that I am referencing the filters in CSS, defined in a separate file.

To test this, I created a MWE that demonstrates the issue. For me, this renders in Firefox and Chrome with nothing in the left box and a gray circle (the expected result) in the right box. This eliminates cause 1, I think. After I did this, I tried:

  1. Referencing using url(.#filter-id), so with a leading dot. This did not change the result.
  2. Moving the defs in the same svg element as where the circle is. This fixes the issue.

My question: is it still possible to somehow reference a filter that is defined in a separate SVG element? I would very much prefer to do it that way in my application. I have read that there are problems with external files, but a different element in the same file should surely be possible?


Solution

  • I think the problem is the display:none in the hidden class, you can use another way to hidden, this for example:

    svg.hidden {
      height:0;
      margin:0;
      border:none;
    }
    

    var svg = d3.select('#container').append('svg'),
        defs = svg.append('defs'),
        dsFilter;
    
    svg.attr('width', 400).attr('height', 200);
    
    dsFilter = defs.append('filter').attr('id', 'grayscale-d3')
    dsFilter.append('feColorMatrix')
      .attr('type', 'matrix')
      .attr('values',
            '0.3333 0.3333 0.3333 0 0 ' +
            '0.3333 0.3333 0.3333 0 0 ' +
            '0.3333 0.3333 0.3333 0 0 ' +
            '0      0      0      1 0')
    
    svg.append('text').attr('x', 2).attr('y', 2).text('appended by D3');
    svg.append('circle').attr('class', 'd3').attr('cx', 200).attr('cy', 120).attr('r', 60);
    body {
      margin: 0;
      font-size: 0;
    }
    
    circle {
      fill: #bada55;
      stroke: #000;
      stroke-width: 1px;
    }
    circle.of {
      filter: url('#grayscale-of');
    }
    circle.d3 {
      filter: url('#grayscale-d3');
    }
    
    svg {
      display: inline-block;
      margin: 1rem 0 0 1rem;
      border: 1px solid #000;
    }
    svg.hidden {
      height:0;
      margin:0;  
      border:none;
    }
    
    text {
      dominant-baseline: text-before-edge;
      font-size: 1rem;
    }
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <svg class="hidden">
      <defs>
        <filter id="grayscale-of">
          <feColorMatrix values="0.3333 0.3333 0.3333 0 0
                                 0.3333 0.3333 0.3333 0 0
                                 0.3333 0.3333 0.3333 0 0
                                 0      0      0      1 0"
                         type="matrix">
          </feColorMatrix>
        </filter>
      </defs>
    </svg>
    <div id="container">
      <svg width="400" height="200">
        <text x="2" y="2">already in page</text>
        <circle class="of" cx="200" cy="120" r="60" />
      </svg>
    </div>