Creating a Dynamic SVG Image with Laravel

James Fairhurst
Apr 5 · 3 min read

Originally published at www.jamesfairhurst.co.uk.

I really like the svg image in the header of the blog, it was taken from SVG Backgrounds and I thought it would be quite cool to try to animate it by updating the colours to convey some sort of wave. It’s not animation as such as it isn’t using CSS to move things but I did want to cycle through the colours.

I first mapped out the colours used $colours = ['364347','465256','566164','667073','767F82','868E91','969D9F','A7ACAE','B7BBBD','C7CACB']; in an array so that I could cycle through them, moving the first colour to the back of the array in an attempt to create motion.

I then created a new route which will act as the dynamic svg image and point that a controller. With Laravel you can take control of the status & headers using View Responses so that you can set the Content-Type so that the browser sees it as an svg:

Route::get('/main-header.svg', 'SvgController@show');

function show()
{
...
return response()
->view('svg', [
'colours' => $colours
], 200)
->header('Content-Type', 'image/svg+xml');
}

The blade template is simply the raw svg image but now we can use our $colours array to specify the fill colours of the different waves:

<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1600 800'>
<rect fill='#ffaa00' width='1600' height='800'/><g>
<path fill='#{{ $colours[0] }}' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/>
<path fill='#{{ $colours[1] }}' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/>
...
</g>
</svg>

In order to make it dynamic I’m using a simple query param to change the index of colours and do some array slicing to move them from the back to the front. e.g. /main-header.svg?i=1, /main-header.svg?i=2 etc

array_merge(array_slice($colours, $request->query('i', 0)*-1), array_slice($colours, 0, count($colours)-$request->query('i', 0)))

Finally a little Javascript is needed to set an interval which will load and update the next image in the sequence and update the css. Initially I was just saving the image src in the loaded array however the browser was re-loading previously displayed images for some reason and there was a slight flash of black as the background-image was set. Saving the entire image object and using that seems to resolve the issue.

var intervalID = window.setInterval(animateHeaderBackgroundSvgImage, 500);
var counter = 1;
var loaded = [];

function animateHeaderBackgroundSvgImage() {
// Reset counter
if (counter >= 10) {
counter = 0;
}

const alreadyLoaded = loaded.filter(img => img.src == window.location.origin+'/main-header.svg?i='+counter);

// Image has already been loaded so just update the css
if (alreadyLoaded.length) {
document.getElementById('main-header').style.backgroundImage = 'url('+alreadyLoaded[0].src+')';
} else {
// Load the next image in the sequence and update the css when fully loaded
img = new Image();
img.onload = function () {
loaded.push(this);
document.getElementById('main-header').style.backgroundImage = 'url('+this.src+')';
};
img.src = window.location.origin+'/main-header.svg?i='+counter;
}

counter++;
}

Although a nice experiment I didn’t really like the final animation enough to deploy it everywhere so I’ve just enabled it for this post but thought a write up would be useful.


Originally published at www.jamesfairhurst.co.uk.

James Fairhurst

Written by

You live till you die - QOTSA