Using feColorMatrix to dynamically recolor icons (part 1, single-color icons)
Avishkar Autar · Aug 29 2018 · Graphics and Rendering
I’ve been experimenting with using feColorMatrix as an elegant way to dynamically color/re-color SVG icons. Here I’ll look at working with single-color icons.
Working directly with the SVG markup
Changing the stroke and/or fill colors of the SVG elements directly can be a good solution in many cases, but it requires:
- Placing the SVG markup into the document to query and modify the appropriate elements when a color update is needed (note that this option isn’t viable if you need to place the icon in an <img> tag or it needs to be placed as a background-image on an element, as you can’t reference the SVG element in such cases)
- Treating the SVG markup as a templated, Javascript, string to make a data-URI, and re-making it when a color update is needed
By using the color transformation matrix provided by feColorMatrix these restrictions go away and we also get back the flexibility of using external files.
Icon color
Keep in mind, we’re only dealing with single-color icons. What color is used doesn’t technically matter, but black is a nice basis and in an actual project, black is beneficial, as you’re able to open your the icon files in an editor or browser and actually see it.
A black pixel within the icon can then be represented by the following vector:
Note that the alpha component may vary due to antialiasing (to smooth out edges) or some translucency within the icon.
The color transformation matrix
feColorMatrix allows you to define a 5×4 transformation matrix. There’s a lot you can do with that, but note that the last column of the matrix is essentially an additive component for each channel (see matrix-vector multiplication), so in that column we enter the desired R, G, B values from top to bottom, which will be added to the zeros in the input vector. Next, we want to preserve the alpha component from the input vector, so the fourth column of the matrix becomes [0, 0, 0, 1]T and the fourth row of the last column is zero, as we don’t want to add anything to the alpha component.
The matrix-vector multiplication gives a new vector (that defines the output pixel) with the entered R, G, B values and the alpha value from the source pixel.
Representing the matrix within an feColorMatrix element is straightforward…
<feColorMatrix in="SourceGraphic" type="matrix"
values="0 0 0 0 R
0 0 0 0 G
0 0 0 0 B
0 0 0 1 0" />
… just plug in values for R, G, B.
Applying the color transformation
The color transformation matrix can be applied to an element by wrapping it in an SVG filter element and referencing the filter via the CSS filter property.
With Javascript, the values attribute of the feColorMatrix element can be updated dynamically. The color change will, in turn, be reflected in any elements referencing the SVG filter.
<!DOCTYPE html>
<html>
<body>
<!--
The values of the color matrix defines the color transformation what will be applied.
Here we just setup the elements and define an identity matrix. We'll modify the matrix via Javascript code
to define an actual color transformation.
-->
<svg style="width:0; height:0; margin:0; padding:0; border:none;">
<filter color-interpolation-filters="sRGB" id="colorTransformFilter">
<feColorMatrix in="SourceGraphic" type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0" />
</filter>
</svg>
<!--
Element with an SVG icon that we want to colorize
Note: that the color transformation is applied to everything not only to the background, but everything
within the element as well.
Typical solution to to isolate background stuff to it's own div and use another div for contents
-->
<div id="logo-colored"
style="
width:300px;
height:300px;
background-color: transparent;
background-image: url(logo.svg);
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
filter:url(#colorTransformFilter);">
<p style="color:#fff">Testing 1 2 3...</p>
</div>
<script type="text/javascript">
/**
* A little helper function to update the color transformation matrix
*
* @param {Number} _r
* @param {Number} _g
* @param {Number} _b
*/
const setPrimaryColor = function(_r, _g, _b) {
const rScaled = _r / 255.0;
const gScaled = _g / 255.0;
const bScaled = _b / 255.0;
const feColorMatrixElem = document.getElementById('colorTransformFilter').getElementsByTagName('feColorMatrix')[0];
feColorMatrixElem.setAttribute(
`values`,
`0 0 0 0 ${rScaled}
0 0 0 0 ${gScaled}
0 0 0 0 ${bScaled}
0 0 0 1 0`
);
};
// Set/update color transformation matrix
setPrimaryColor(129, 0, 0);
</script>
</body>
</html>
The code will take a black-colored icon and re-color it to [129, 0, 0], as seen below:
Limitations
This technique provides a lot of flexibility, but it’s not without it’s limits.
- For icons applied as background-images, the CSS filter property isn’t ideal. CSS filter will effect not only the element it’s applied to, but all child elements as well. Note the “Testing 1 2 3…” paragraph is re-colored in the demo above.
- As is the case with mixing the CSS filter property and the SVG filter element, effects governed by the CSS transition property won’t work.
Similar techniques
- For shifting between colors, the hue-rotate() CSS filter function can be a solution. However, in practice, I don’t find this intuitive and color changes are rarely just hue rotations.
- A more limited case, transitioning a colored icon to white, can be achieved with 2 CSS filter functions, brightness(0) and invert(100%).
- You can do crazier things by trying to compute and fit a solution to the hue-rotation, saturation, sepia, and invert filter functions; however this is both complex to grasp and produces inexact/approximate color matches.
- An SVG filter using feComponentTransfer should work, but I don’t find it as intuitive to work with.
Resources
If you want to interactively play around with feColorMatrix, check out SVG Color Filter Playground.