I’ve been playing around a bit with SVG recently as I’ve worked on my SVG CSS Backgrounds Generator.
One idea I had was of “layers” of material that could catch the light independently:
Here’s how I created the effect.
I’m using svg.js and svg.filters.js to generate SVG on-the-fly from a little bit of Javascript. Svg.js provides an API for creating and manipulating SVG, and adding it into the DOM. Svg.filters.js adds in support for SVG filters, which I’m using to generate the drop shadows.
The markup here is very simply, just a div
that will hold the SVG element:
<style>
#svg-container {
width: 640px;
height: 240px;
}
</style>
<div id="svg-container"></div>
In real life you probably wouldn’t hard-code values like that. This is just a demo. There are going to be plenty more arbitrary constants in this demo…
My script loads once jQuery is ready and creates the SVG drawing element based one some hard-coded sizes. I allow a little bit of space all around to display the drop shadows a bit more nicely.
$(function() {
const offset = 20;
const blockSize = 20;
const widthInBlocks = 30;
const heightInBlocks = 10;
const draw = SVG().addTo('#svg-container').size(
widthInBlocks * blockSize + offset * 2,
heightInBlocks * blockSize + offset * 2
);
...
});
There are three layers in this demo, each one being an SVG <path>
element. I iterate the blocks in the display and randomly assign each block to a layer. When drawing a block, I append an absolute “move” command (capital M
) followed by four relative drawing commands (lowercase h
and v
commands).
let paths = ['', '', ''];
for (let by = 0; by < heightInBlocks; ++by) {
for (let bx = 0; bx < widthInBlocks; ++bx) {
let pathIndex = 0;
const rnd = Math.random();
const edgeCutoff = 2;
const isEdge = (bx < edgeCutoff) || (by < edgeCutoff) || (bx >= widthInBlocks-edgeCutoff) || (by >= heightInBlocks-edgeCutoff);
if (rnd < 0.333) {
pathIndex = 0;
}
else if (rnd < 0.66) {
pathIndex = 1;
} else {
pathIndex = 2;
}
if (isEdge && Math.random() < 0.4) continue;
paths[pathIndex] += ` M${bx*blockSize+offset},${by*blockSize+offset} h${blockSize} v${blockSize} h${-blockSize} v${-blockSize}`;
}
}
I wanted gaps at the edges of the rectangle, so there is an extra check to see if blocks in the outer two rows or columns should be skipped.
At this point, the blocks are not styled in any way and won’t be visible. There are three components I need to add now:
let colors = ['#262e64', '#414b90', '#676fa8'];
let highlights = ['#9ca1bf', '#9ca0bf', '#dadbe1']
for (let pi = 0; pi < paths.length; ++pi) {
let path = draw.path(paths[pi]);
const color = colors[pi];
const highlight = highlights[pi];
const spread = (3 - pi) * 0.05;
const gradient = draw.gradient('linear', function(add) {
add.stop(0, color);
add.stop(0.5-spread, color);
add.stop(0.5, highlight);
add.stop(0.5+spread, color);
add.stop(1, color);
});
gradient.attr({gradientUnits: 'userSpaceOnUse'});
gradient.attr({gradientTransform: 'rotate(15)'});
const offset = -30 * pi;
const animate = SVG(`<animateTransform attributeName="gradientTransform" attributeType="XML" type="translate" values="${offset-500},0; ${-offset+500},0" repeatCount="indefinite" dur="4s" additive="sum" />`);
gradient.add(animate);
path.fill(gradient);
path.filterWith(function(add) {
const blur = add.offset(0, 2).in(add.$sourceAlpha).gaussianBlur(2);
add.blend(add.$source, blur)
});
}
(Unfortunately I couldn’t find a way of creating the <animateTransform>
element using svg.js, so I had to drop down to raw SVG XML.)
I think it turned out quite well – a pretty simple but striking effect. The main downside of this approach is that it’s pretty CPU-heavy. If you were using this for real you’d probably want to either reduce the size of the graphic, or only run the animation once.
You’re welcome to use the code presented on this page in your own projects. (I’d recommend cleaning it up a bit first however…)
Published: Monday, August 10, 2020
Hackification.io is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com. I may earn a small commission for my endorsement, recommendation, testimonial, and/or link to any products or services from this website.