title: How to use a simple SVG filter on Safari with acceptable performance and without crashing?
body: <p><em>Disclaimer: This post is half question and half report of my experiments while trying to find a solution.</em></p>
  <h2>The Task: Simple SVG Filter on a Monochrome Rectangle</h2>
  <p>Using a filter to change or modify the color of a monochrome shape in SVG is pretty straightforward. Here is how it can be done:</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;svg viewBox="0 0 460 130"&gt;
    &lt;defs&gt;
      &lt;filter id="filter1"&gt;
        &lt;feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" /&gt;
      &lt;/filter&gt;
    &lt;/defs&gt;
    &lt;g transform="translate(20, 0)"&gt;
      &lt;text x="0" y="35"&gt;Original&lt;/text&gt;
      &lt;rect x="0" y="50" width="200" height="80" fill="red" /&gt;
    &lt;/g&gt;
    &lt;g transform="translate(240, 0)"&gt;
      &lt;text x="0" y="35"&gt;Filtered&lt;/text&gt;  
      &lt;rect x="0" y="50" width="200" height="80" fill="red" filter="url(#filter1)" /&gt;
    &lt;/g&gt;
  &lt;/svg&gt;</code></pre>
  </div>
  </div>
  </p>
  <p> </p>
  <h2>The Problem: It is Slow on Safari and Crashes on Safari Mobile</h2>
  <p>The little problem with this is that it is really slow on Safari. In the snippet below a new filtered rect is added to the SVG every half a second. This will eventually crash on the Safari mobile after 10-80 cycles, depending on your device. Also, it is unbearably slow. For comparison, on an entry level Android phone running Chrome this runs almost forever with 50 fps at the beginning.</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var rectCount = 1;
  var svgRectElem = document.getElementById('svg-rect');
  var rectCountElem = document.getElementById('rect-count');
  var fpsElem = document.getElementById('fps');
  
  (function insertRect() {
    window.requestAnimationFrame(function() {
      var start = new Date().getTime();
    
      var clonedSvgRectElem = svgRectElem.cloneNode();
  
      // insert cloned rect SVG element
      svgRectElem.parentNode.appendChild(clonedSvgRectElem);
  
      rectCountElem.textContent = ++rectCount;
  
      window.requestAnimationFrame(function() {
        var fps = Math.round(100000 / (new Date().getTime() - start)) / 100;
        fpsElem.textContent = fps;
        window.setTimeout(insertRect, 500);
      }, 100);
    });
  })();</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;p&gt;SVG Rect Count: &lt;span id="rect-count"&gt;1&lt;/span&gt;&lt;/p&gt;
  &lt;p&gt;fps: &lt;span id="fps"&gt;&lt;/span&gt;&lt;/p&gt;
  
  &lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0,0,1024,1024"&gt;
    &lt;defs&gt;
      &lt;filter id="filter-1"&gt;
        &lt;feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" /&gt;
      &lt;/filter&gt;
    &lt;/defs&gt;
    &lt;rect id="svg-rect" fill="red" filter="url(#filter-1)" x="0" y="0" width="1024" height="1024" /&gt;
  &lt;/svg&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>    <a href="https://codepen.io/anon/pen/BGyNdb" rel="noreferrer">Snippet on CodePen</a></p>
  <p> </p>
  <h2>Possible Solutions: What I Came up with so far</h2>
  <p>I tried several approaches, but as you will see. As you will see, some of them work for Safari, each one has a catch.</p>
  <p> </p>
  <h3>1. Wait for a fix by the WebKit team</h3>
  <p><em>tl;dr Maybe works, maybe not</em></p>
  <p>That would be the most convenient option, but if you consider that SVG filters were introduced 15 years ago, I would not hold my breath for this issue to be fixed anytime soon. And yes, I have written a bug report.</p>
  <p> </p>
  <h3>2. Use <code>filterRes</code></h3>
  <p><em>tl;dr Works, but only until the next Safari release</em></p>
  <p>In SVG 1.1 the attribute <code>filterRes</code> is defined. This specifies the resolution of the filter. A value of 1 means that the source for the filter will be treated as a single pixel. So instead of running over all pixels, the filter has to be applied only to one pixel. Since we are using a single color rectangle as the input image, treating the whole image as one pixel is ok in our case.</p>
  <p>In our example the filter will look like this:</p>
  <pre><code>&lt;filter id=&quot;filter1&quot; filterRes=&quot;1&quot;&gt;
    &lt;feColorMatrix values=&quot;0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0&quot; /&gt;
  &lt;/filter&gt;
  </code></pre>
  <p><a href="https://codepen.io/anon/pen/ZmYbpX" rel="noreferrer">Snippet on CodePen</a></p>
  <p>This actually improves performance a lot and no crashes occur!</p>
  <p><strong>The sad news</strong> is that <code>filterRes</code> has been removed from the SVG standard in version 2.0. WebKit was very fast to adapt this part of SVG 2.0 and decided to remove it in the next version. If we rely on <code>filterRes</code>, the same problem will appear again soon.</p>
  <p> </p>
  <h3>3. Filter a small rect, then scale up</h3>
  <p><em>tl;dr Does not work</em></p>
  <p>The approach above shows that applying the filter on a small regions can improve performance a lot. There we apply the filter on a small rect, and then scale the result up to the desired size. For scaling we use the <code>transform</code> attribute. If we set the rect to 1/64 of the desired size we will then scale it with <code>transform=&quot;scale(64)&quot;</code> to achieve a final size of 1024.</p>
  <p><a href="https://codepen.io/anon/pen/pQvjPw" rel="noreferrer">Snippet on CodePen</a></p>
  <p>Unfortunately this does not work at all. Performance is unchanged and it still crashes on the Safari mobile. Interestingly this cannot be attributed to the filter or transform alone. Only in combination this gets slow and crashes.</p>
  <p> </p>
  <h3>4 Filter a small rect, then use <code>feTile</code> to fill the Target rect</h3>
  <p><em>tl;dr Works a bit, but artefacts on Chrome</em></p>
  <p>This apporach is similar to the previous. We apply the filter to a small rectangle first, and then use a second filter with <code>feTile</code> to fill the target rectangle.</p>
  <p><a href="https://codepen.io/anon/pen/bQNwXm" rel="noreferrer">Snippet on CodePen</a></p>
  <p>This works on Safari mobile. Not as good as <code>filterRes</code>, but there is quite an improvement.</p>
  <p>But for this approach <strong>Chrome is the party pooper</strong>. If you scale zoom in and out on Chrome, in some zoom levels a raster is displayed. Here is how it can look (correct would be one large square):</p>
  <p><a href="https://i.stack.imgur.com/VOwfE.png" rel="noreferrer"><img src="https://i.stack.imgur.com/VOwfE.png" alt="enter image description here" /></a></p>
  <p> </p>
  <h3>5 Use huge values for the filter's width and height</h3>
  <p><em>tl;dr Works, but a hack and causes issues on Internet Explorer</em></p>
  <p>This is a very counter intuitive approach. Setting the width and height of the filter region to very large values should make the filter much slower, because zillions of pixels will have to be processed. But actually the opposite is the case. The filter speeds up dramatically on WebKit and no crashing occurs.</p>
  <p>Here is how to implement this in our example:</p>
  <pre><code>&lt;filter id=&quot;filter-1&quot; x=&quot;0&quot; y=&quot;0&quot; width=&quot;102400&quot; height=&quot;102400&quot; filterUnits=&quot;userSpaceOnUse&quot;&gt;
    &lt;feColorMatrix values=&quot;0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0&quot; /&gt;
  &lt;/filter&gt;
  </code></pre>
  <p><a href="https://codepen.io/anon/pen/bQNwqa" rel="noreferrer">Snippet on CodePen</a></p>
  <p>I think what is happening here is that filter first scales down the original filter region, then processes the part that will be visible in the SVG's viewBox (which is a tiny region after downscaling), and then scales that region up again. And this appears to be fast.</p>
  <p><strong>Unfortunately</strong> this is quite a hack and we do not now how future browser versions will handle SVGs with such large values. Also, this causes issues on Internet Explorer, where the document's scrolling area will become HUGE (probably because the filter's bounds are calculated into the size), making scrolling almost impossible. And if you select the values too small, Internet Explorer will actually display the rect in the size of the filter's bounds.</p>
  <p> </p>
  <h2>What is the solution?</h2>
  <p>Actually, I do not know. There is an easy solution using <code>filterRes</code>, but this will expire soon (it is already not supported in WebKit technology preview), so this is not an option. All other approaches cause issues on other browsers.</p>
  <p>Can you think of any other approach that I could try? I cannot believe that cross browser use of SVG filters fifteen years after the SVG 1.1 spec was released is such an adventure.</p>