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.
- <svg> The main SVG element that will hold all other elements.
- <defs> Here we define filters that we can attach to our graphic elements. Child of svg.
- <filter> The filter combines all of the style layers that we will want to apply. Child of defs.
- <feSpecularLighting> A basic lighting layer that controls color and intensity. Child of filter.
- <fePointLight> A modifier for feSpecularLighting that controls size and placement. Child of feSpecularLighting.
- <feComposite> This allows us to add a filter to a graphic object. Child of filter.
- <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.
[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.