SVG is commonly associated with regular geometric designs that feature hard edges, but with a little cleverness, it can also be used to create random fuzziness.
“Bokeh” is the out-of-focus effects produced by a lens, particularly one associated with a limited depth of field. Visually, this can appear as a pleasant blur, or distorted points of light in different shapes; here, I’m using circles.
This effect has been popularly rendered (and often animated) using <canvas>
, but I wanted to create a variant that could be applied as a pure background image. This meant that the blur on each of the circles had to be rendered inside the SVG itself: the CSS blur()
filter does not work as one might expect on SVG elements.
Bokeh is, by its very nature, semi-random, so at the core of this effect are two quick functions that create two different kinds of randomness: one a random floating point number (to two decimal places), the other a whole number.
Random Seeds
var randomFloat = function(min,max) {
return (Math.random() * (max - min) + min).toFixed(2);
}
var randomRange = function(min,max) {
return Math.floor(Math.random()*(max-min+1)+min);
}
Coupled with these functions are a bunch of variables as upper and lower limits:
- the minimum and maximum number of circles
- their min and max radius
- and their upper and lower opacity
var minRad = 9,
maxRad = 15,
minCircs = 10,
maxCircs = 25,
minOpacity = 0.2,
maxOpacity = 0.8,
After some experimentation I found that best and most realistic visual results were produced using similar hues that varied in saturation and luminosity, making HSLa color an obvious choice for rendering the circles. (The a
component is provided by the two opacity variables defined earlier). These choices required more variables:
var hue = 300,
minSat = 25,
maxSat = 75,
minLum = 25,
maxLum = 75;
Finally, each circle needed to be blurred:
var minBlur = .75,
maxBlur = 4,
blurVariants = 3;
Rendering the Result
While it would be completely possible to complete the effect as an actual SVG element, I opted to create the result as a string, concatenating changes as the script progressed, which made the result easier to convert into base64 later. (More on that in a moment).
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">';
Each of the blur effects is rendered as a separate SVG filter, each with a unique id
:
svg += '<defs>';
for (var i=0; i < blurVariants; i++) {
svg += '<filter id="bokeh'+i+'">';
svg += '<feGaussianBlur stdDeviation="'+randomFloat(minBlur, maxBlur)+"'></feGaussianBlur>";
svg += '</filter>';
}
svg += '</defs>";
Finally, each of the circles is rendered out using the limits set by the variables defined earlier:
for (var i=1;i < randomRange(minCircs, maxCircs);i++) {
svg += '<circle ";
svg += 'r="'+randomRange(minRad, maxRad)+'%"';
svg += 'cx="'+randomRange(0, 100)+'%"';
svg += 'cy="'+randomRange(0, 100)+'%"';
svg += 'fill="hsla('+hue+', '+randomRange(minSat, maxSat)+'%, '+randomRange(minLum, maxLum)+'%, '+randomFloat(minOpacity, maxOpacity)+')"';
svg += ' filter="url(#bokeh'+randomRange(0,blurVariants)+")';
svg += '></circle>;;
}
svg += '</svg>';
Deep Background
Placing the SVG as a string for the background (background-image: url("<svg>…")
) can be problematic, and has poor cross-browser compatibility. Instead, I opted to convert the SVG string into base64, and apply that as the page background via JavaScript:
var encodedData = window.btoa(svg),
url = "data:image/svg+xml;base64," + encodedData;
document.body.style.backgroundImage = "url("+url+")";
Making The Demo
To provide some user control - rather than simply refreshing the page to get a new result - I allowed the user to alter one of the variables in the demo. This was enabled by turning the SVG creation part of the script into a function, and one of the variables into reading a range
input:
var hue = document.getElementById("hueangle").value;
Any changes to this slider then call the function, recreating the background with new random results:
hueangle.addEventListener("change", function() {
svg();
})
You can learn more about these changes by inspecting the associated CodePen demo.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/GJemEX