Dynamically generated SVG through SASS + A 3D animated RGB cube!

Screenshot of the cubeToday, I was giving the opening keynote at Codemania in Auckland, New Zealand. It was a talk about color from a math/dev perspective. It went quite well, despite my complete lack of sleep. I mean that quite literally: I hadn’t slept all night. No, it wasn’t the jetlag or the nervousness that kept me up. It was my late minute decision to replace the static, low-res image of an RGB cube I was using until then with a 3D cube generated with CSS and animated with CSS animations. Next thing I knew, it was light outside and I had to start getting ready. However, I don’t regret literally losing sleep to make a slide that is only shown for 20 seconds at most. Not only it was super fun to develop, but also yielded a few things that I thought were interesting enough to blog about.

The most challenging part wasn’t actually the 3D cube. This has been done tons of times before, it was probably the most common demo for CSS 3D transforms a couple of years ago. The only part of this that could be of interest is that mine only used 2 elements for the cube. This is a dabblet of the cube, without any RGB gradients on it:

The challenging part was creating the gradients for the 6 sides. These are not plain gradients, as you can see below:

RGB cube sidesThese are basically two linear gradients from left to right, with the topmost one being masked with a gradient from top to bottom. You can use CSS Masking to achieve this (for Chrome/Safari) and SVG Masks for Firefox, but this masks the whole element, which would hide the pseudo-elements needed for the sides. What I needed was masks applied to backgrounds only, not the whole element.

It seemed obvious that the best idea would be to use SVG background images. For example, here is the SVG background needed for the top left one:

<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px">

<linearGradient id="yellow-white" x1="0" x2="0" y1="0" y2="1">
	<stop stop-color="yellow" />
	<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient id="magenta-red" x1="0" x2="0" y1="0" y2="1">
	<stop stop-color="red" />
	<stop offset="1" stop-color="magenta" />
</linearGradient>
<linearGradient id="gradient" x1="0" x2="1" y1="0" y2="0">
	<stop stop-color="white" />
	<stop offset="1" stop-color="black" />
</linearGradient>
<mask id="gradient-mask">
	<rect width="100%" height="100%" fill="url(#gradient)"/>
</mask>

<rect width="100%" height="100%" fill="url(#yellow-white)"/>
<rect width="100%" height="100%" fill="url(#magenta-red)" mask="url(#gradient-mask)"/>

</svg>

However, I didn’t want to have 6 separate SVG files, especially with this kind of repetition (cross-linking to reuse gradients and masks across different files is still fairly buggy in certain browsers). I wanted to be able to edit this straight from my CSS. And then it hit me: I was using SASS already. I could code SASS functions that generate SVG data URIs!

Here’s the set of SVG generating SASS functions I ended up writing:

@function inline-svg($content, $width: $side, $height: $side) {
	@return url('data:image/svg+xml,#{$content}');
}

@function svg-rect($fill, $width: '100%', $height: $width, $x: '0', $y: '0') {
	@return unquote('');
}

@function svg-gradient($id, $color1, $color2, $x1: 0, $x2: 0, $y1: 0, $y2: 1) {
	@return unquote('

	');
}

@function svg-mask($id, $content) {
	@return unquote('#{$content}');
}

And then I was able to generate each RGB plane with another function that made use of them:

@function rgb-plane($c1, $c2, $c3, $c4) {
	@return inline-svg(
		svg-gradient('top', $c1, $c2) +
		svg-gradient('bottom', $c3, $c4) +
		svg-gradient('gradient', white, black, 0, 1, 0, 0) +
		svg-mask('gradient-mask', svg-rect('url(%23gradient)')) +
		svg-rect('url(%23bottom)') +
		svg-rect('url(%23top)" mask="url(%23gradient-mask)')
	);
}

/* ... */

.cube {
	background: rgb-plane(blue, black, aqua, lime);

	&::before {
		background: rgb-plane(blue, fuchsia, aqua, white);
	}

	&::after {
		background: rgb-plane(fuchsia, red, blue, black);
	}
}

.cube .sides {
	background: rgb-plane(yellow, lime, red, black);

	&::before {
		background: rgb-plane(yellow, white, red, fuchsia);
	}

	&::after {
		background: rgb-plane(white, aqua, yellow, lime);
	}
}

However, the same functions can be used for all sorts of SVG backgrounds and it’s very easy to add a new one. E.g. to make polygons:

@function svg-polygon($fill, $points) {
	@return unquote('');
}

@function svg-circle($fill, $r: '50%', $cx: '50%', $cy: '50%') {
	@return unquote('');
}

You can see the whole SCSS file here and its CSS output here.

Warning: Keep in mind that IE9 and some older versions of other browsers have issues with unencoded SVG data URIs. Also, you still need to escape hashes (%23 instead of #), otherwise Firefox fails.

  • bc

    Links at the end are broken.

    • http://lea.verou.me/ Lea Verou

      Thanks, fixed!

      • neapel

        awesome technique!
        another bug report: the code blocks are missing the elements inside unquote()

        (ugh the image shows up 4 times? feel free to delete the comment)

  • Pablo Chiappetti

    Very interesting read Lea! Thanks a lot for sharing :)

  • Yulya Buhvalova

    Magic! Thank you for sharing this : )

  • http://codecondo.com/ Alex

    Great work as always, thank you! :)

  • maxw3st

    Nicely done! Great demo of the SVG code needed.

  • Pingback: Collective #110 » CSS 3 & HTML 5 Links und Infos

  • Pingback: Collective #110 | Zipsite.netZipsite.net

  • Pingback: Collective #110 - InfoLogs

  • Pingback: Collective #110 | Daniele Milana

  • Jamie Anderson

    I don’t think anyone in the audience really noticed the lack of sleep. It came across more as jetlag than anything else. No matter how you look at it though, it was still a fantastic presentation.

    Thanks so much for coming down to little ol’ NZ for Codemania, and especially for running the workshop. I got so much useful info out of it, and can’t wait to try putting it to good use!

    • http://lea.verou.me/ Lea Verou

      Aww, thanks! So glad you enjoyed it!!

  • o3bla

    Grazie! Really useful.

  • Pingback: TopImpress - Design Tips, Materials and Inspiration

  • Pingback: Collective #110 | Web Design Factory

  • Pingback: Best materials from the world of CSS (30 March — 5 April 2014)

  • http://adonisk.com Adonis K.

    Επική όπως πάντα! Thanks for the inline-svg via function idea

  • http://www.friv4gaming.com/ Friv 4

    I’m happy about what you bring it very rewarding

  • http://www.friv2game.org/ friv2game

    Very intersting. This article is very useful for my work. Thanks

  • http://www.kizilove.in/ Kizi Love

    Very intersting.

  • Pingback: A Compendium of SVG Information | Lunarium Design

  • Pingback: A Compendium of SVG Information | Vips

  • Brad Kemper

    Too bad we can’t have ink effects on background layers. Otherwise you could just do the first box as something like this:

    background:
    linear-gradient(
         to bottom,
         yellow,
         rgba(255,255,255,0)
    ) multiply,
    linear-gradient(
         to left,
         magenta,
         rgba(255,255,255,0)
    );

  • Pingback: Trucos de diseño - Artysmedia

  • Pingback: Trucos de diseño web - Artysmedia

  • isochronous

    This was extremely helpful, thank you! I can verify that the contents of everything inside of the quote(”) and unquote(”) calls is missing on this page, though of course downloading the actual files neatly side-steps that problem. I should also mention that on my machine (Windows Server 2012 R2) putting line breaks in any of the content actually renders a characters in the output. This is true for line breaks in the SVG text as well as line breaks between the different calls to the functions you’ve defined. The problem occurs using both “regular” (Ruby) sass compilation as well as when using libsass. Thankfully, removing the line breaks of course fixes the problem.