Log

View Options

Dynamic SVG Filters

6/3/20

When redesigning my (this) site recently I wanted to do some fun things with dynamic display using SVG elements. If you have noticed the background is always different shapes and colors. This is because it is made up of randomly generated SVG elements. You may also notice that there is a shine to some of the edges. This is because of dynamically generated SVG filters applied to the shapes.

Here I'm going to share the techniques on making your own dynamic SVG filters. You will need some understanding of SVG filters to follow along. For this exercise we're going to create a light filter like the example below.

Light Filter Controls


Step 1: Element Setup

Let's set up our elements. First we'll add a placeholder element where we'll add our SVG content later. [HTML] <div id="svg_wrap">   <!-- Dynamic content --> </div> It's important to note SVG uses DOM2 which means referencing them in JavaScript isn't exactly the same as referencing a HTMLElement.

To create an SVG element we'll want to specify the namespace where our element is defined. For all SVG elements you can use http://www.w3.org/2000/svg. To apply the namespace when creating elements we'll use createElementNS. Let's start and build all of the basic SVG elements.

  1. <svg> The main SVG element that will hold all other elements.
  2. <defs> Here we define filters that we can attach to our graphic elements. Child of svg.
  3. <filter> The filter combines all of the style layers that we will want to apply. Child of defs.
  4. <feSpecularLighting> A basic lighting layer that controls color and intensity. Child of filter.
  5. <fePointLight> A modifier for feSpecularLighting that controls size and placement. Child of feSpecularLighting.
  6. <feComposite> This allows us to add a filter to a graphic object. Child of filter.
  7. <circle> This is our graphic element. Child of svg.

Let's see what this looks like in script. [JavaScript] var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); var fe_spec_light = document.createElementNS("http://www.w3.org/2000/svg", "feSpecularLighting"); var fe_point_light = document.createElementNS("http://www.w3.org/2000/svg", "fePointLight"); var fe_comp = document.createElementNS("http://www.w3.org/2000/svg", "feComposite"); var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");

Cool, now we have all of the elements we'll be using to build our example.

Step 2: Attribute Setup

Next we'll set the attributes to our elements. Unlike the SVG elements, a namespace isn't required when manipulating element attributes. So, when calling setAttributeNS the first parameter will be null.

I have added comments to the code sample below to explain some of the more esoteric attributes. [JavaScript] // Set attributes for the svg element svg.setAttributeNS(null, "viewBox", "0 0 100 100"); // The display area of our SVG canvas svg.setAttributeNS(null, "width", "100"); // The width of our SVG canvas svg.setAttributeNS(null, "height", "100"); // The height of our SVG canvas // Set attributes for the filter element filter.setAttributeNS(null, "id", "circle_fitler"); // For refercning when applying the filter to our circle element // Set attributes for the feSpecularLighting element fe_spec_light.setAttributeNS(null, "result", "spec_light"); // For refercning in feComposite in2 attribute fe_spec_light.setAttributeNS(null, "specularConstant", "2"); // The hardness of the light fe_spec_light.setAttributeNS(null, "specularExponent", "50"); // The spread of the light fe_spec_light.setAttributeNS(null, "lighting-color", "#fff"); // The color of the light // Set attributes for the fePointLight element fe_point_light.setAttributeNS(null, "x", "50"); // The X position of the light fe_point_light.setAttributeNS(null, "y", "20"); // The y position of the light fe_point_light.setAttributeNS(null, "z", "50"); // The z or distance of the light // Set attributes for the feComposite element fe_comp.setAttributeNS(null, "in", "SourceGraphic"); // The element to apply the filter to. SourceGraphic means the origin element fe_comp.setAttributeNS(null, "in2", "spec_light"); // The id of the filter to apply fe_comp.setAttributeNS(null, "operator", "arithmetic"); // This allows us to combine the outcomes of our two in targets fe_comp.setAttributeNS(null, "k1", "1"); // Light source inside of SourceGraphic fe_comp.setAttributeNS(null, "k2", "1"); // SourceGraphic opacity fe_comp.setAttributeNS(null, "k3", "0"); // Setting this to 0 prevents the transparent background from getting light fe_comp.setAttributeNS(null, "k4", "0"); // Setting this to 0 prevents the transparent foreground from getting light // Set attributes for the circle element circle.setAttributeNS(null, "cx", "50"); // Start X position circle.setAttributeNS(null, "cy", "50"); // Start Y position circle.setAttributeNS(null, "fill", "rgb(153, 153, 255)"); // Circle fil color circle.setAttributeNS(null, "r", "50"); // Circle radius circle.setAttributeNS(null, "style", "filter: url(#circle_fitler);"); // Point to our filter id to apply it

Step 3: Construct the Elements

Now we just need to put the elements together in the right order. Use the elements list above if you get confused on what element should be a child of another element. [JavaScript] fe_spec_light.appendChild(fe_point_light); filter.appendChild(fe_spec_light); filter.appendChild(fe_comp); defs.appendChild(filter); svg.appendChild(defs); svg.appendChild(circle); // Target the element where we add all of the elemtns let svg_wrap = document.getElementById("svg_wrap"); // Use the id of the element you want to add everything to svg_wrap.appendChild(svg);

Now you should be able to run your code and see your circle with the pin light filter applied to and since we have JavaScript references to all of the elements we just built we can easily change their attributes.

Step 4: Applying Controls

Now, there are a whole bunch of ways we could manipulate our light. We could use a setTimeout to gradually move it. We could make it blink. We could make it pulse. For the purposes of this experiment we're going to use range sliders to adjust our light.

Here is the markup for the range sliders. [HTML] <div>   <input id="pinx" name="pinx" type="range" min="0" max="100" step="1" value="50"/>   <label for="pinx">X</label>   <br/>   <input id="piny" name="piny" type="range" min="0" max="100" step="1" value="20"/>   <label for="piny">Y</label>   <br/>   <input id="pinz" name="pinz" type="range" min="0" max="100" step="1" value="50"/>   <label for="pinz">Radius</label> </div> Note the id attribute of the inputs. We'll be using these.

Next we will assign the functionality to our control inputs. [JavaScript] // Get referces to control inputs ctrl_pinx = document.getElementById("pinx"); ctrl_piny = document.getElementById("piny"); ctrl_pinz = document.getElementById("pinz"); // Create contorl function function updateFitlers(e) {   let fx = ctrl_pinx.value;   let fy = ctrl_piny.value;   let fz = ctrl_pinz.value;   fe_point_light.setAttributeNS(null, "x", fx + "");   fe_point_light.setAttributeNS(null, "y", fy + "");   fe_point_light.setAttributeNS(null, "z", fz + ""); }; // Assign functionality ctrl_pinx.addEventListener("input", updateFitlers); ctrl_piny.addEventListener("input", updateFitlers); ctrl_pinz.addEventListener("input", updateFitlers);

Conclusion

Congratulations, if you've been following along you should have your own dynamic SVG filter working. If not you can download the example package and have some fun with it.