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 . 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