For better or worse, extruded text was a staple of the mid-90s desktop publishing design landscape. It was rare for a party invitation or gaming fanzine not to be blessed by this 3D text effect on its way out the printer.
As design was increasingly destined for screen rather than page, extrusion fell out of favour. But recently, I've noticed a quiet revival.
Nowadays, it's usually with a flat-shade effect of a different colour, rather than the aggressive faux 3D of the past. My favourite example so far is on the Lamanna's website:
In this tutorial, we're going to first create this extruded text effect using CSS. Then we'll create a performant staggered animation like this:
We'll also discover some cross-browser issues that I bumped into while creating this technique. They have serious and surprising implications, both for the kind of extrusions we can create and the performance of animating them.
So, let's begin!
Extrusion can be used with any HTML, applied with any CSS selector. The only rule is that the text you apply it to is big and bold. Otherwise it won't look good!
The effect is made with the CSS
text-shadow property. It can be used to draw multiple shadows, by comma-separating them:
By positioning these shadows at
1px increments on both axes, we can create an extrusion effect where the text appears to have depth:
Every shadow we add gives the text an extra pixel of depth.
We can also change the direction of the effect by changing the offset of the shadows. For instance, to make text pop out from the top left, we can use increments of
1px increments we're drawing enough shadows to create the illusion of a smooth line in most instances. However, where text comes to sharp points, you might be able to make out jagged edges like this:
Ideally, we would more draw shadows at smaller
0.5px increments to create some sub-pixel smoothing. This actually works in Chrome, but unfortunately Firefox and Safari round
text-shadow x and y origin coordinates to the nearest pixel. Drawing extra shadows has a performance cost, so in these browsers we'd be doubling our shadow count without improving visual quality.
This origin rounding has a more serious implication. The jagged edges aren't so noticeable when shadows are drawn at 45 degree diagonals because the stepping is regular. But when trying to make other angles, the stepping becomes irregular and makes lines that look like this:
This is a lot more noticeable, so for this reason I would stick to making extrusion effects at 45 degree angles unless you're specifically targeting Chrome's renderer.
As we've seen, creating deeper extrusions requires adding more shadows. As these shadow lists grow they become harder to read, maintain and reuse.
Because of this, I highly recommend dynamically generating these shadows!
text-shadow value of the length specified by
When you're generating shadows dynamically it's easier to be creative. For real Microsoft Publisher 97 vibes you could change the
extrude function to create a darker shadow on each loop iteration:
...Okay, maybe we don't need to take it that old-school. But hopefully this gives you an idea of some of the ways you can play around with dynamic generation.
Hey there! I'm Matt Perry, and I'm creating Motion.dev to be an educational resource for motion on the web.
To stay updated with my progress, sign up to the newsletter!
CSS offers two ways to apply animations,
animation. A full discussion of the pros and cons of each will come in the future, but because this is a simple transition between two visual states a good rule of thumb is to use
transition defines an animation to use whenever the named CSS styles change. In our case, we want to animate
text-shadow, so we can apply a
transition to our element like so:
The most basic syntax of
property time easing. Here, we're saying we want to animate
text-shadow for 600 milliseconds, with the a cubic bezier curve of 0.22, 0.12, 0.02, 1.26. This curve will start the animation very fast, provide a little overshoot for that bouncy effect, before coming to rest.
transition applied, whenever
text-shadow changes on this
span, it will animate. It doesn't matter how the property changes. It can be via a psuedo-selector:
Direct assignment to
In this tutorial we're going to trigger the animation by adding class:
So far, we're only animating
text-shadow, so the extrusion looks like it's animating out from the stationary text. This can be a nice effect in its own right with shallower extrusions, but we can also make the text itself look like it's popping out of the page, by adding a
transform style that moves the text by the opposite distance of the shadow.
First, we want to change our
transition to animate
all. This will animate any style prop that changes with the same animation.
To make the
transform, out of habit I'd normally add this with
translate3d. This forces the browser to hardware-accelerate the animation with the GPU, usually leading to smoother motion. (You can also use
will-change to do this, but it's more of a polite hint.)
This looked and worked great in most browsers, but on iOS Safari, it looked like this:
I don't know exactly why this was happening, but my hunch was some kind of performance trick to reduce GPU thrashing. Which would make a likely fix changing
transform to a non-hardware accelerated value:
text-shadow already triggers layout(!) and style recalculations, the GPU acceleration probably wasn't doing anything in this instance anyway. From testing, the resultant animation performed better, even on throttled devices:
In our initial example staggered the animation of each line. To stagger the animation of different elements they each need to be wrapped in a separate tag.
Change the class selector to apply the extrusion effect to these
Now we can add a separate
transition-delay to each
span by using the
nth-child selector. We want to delay the second and third child, so we use
In the original Lammana's example, the text had an outline the same color as the extrusion shadow. This makes it pop, improving readability.
They implemented this with the non-standard
-webkit-text-stroke property. Despite being non-standard, I was surprised to see it actually enjoys wide compatibility with every modern browser, and has done for a long time! So I added it to our example.
Check out the animation it created:
If you're viewing this animation in Chrome or Firefox, the animation should perform as you'd expect. But in Safari on a 2016 MacBook, it was performing poorly:
transform itself seems to be animating at 60fps but the shadows are being drawn in very erratically.
Recording the animation using Safari's Timelines tool was fruitless. Style, paint and layout seemed to be under control, but there was a mysterious "other" column eating up 100ms per frame.
My heart sank because this isn't an acceptable quality for an animation and I was half way through writing this tutorial! But I remembered I had made this animation once before where it performed well, and the only discernable difference was the
-webkit-text-stroke property. I turned it off and... 60fps!
I was ready to accept that you simply shouldn't use
-webkit-text-stroke in animations where you're performing other paints, until I realised we're already animating up to 30 shadows to create this effect, so what's a few more?
The trick with the text outline in our finished product is that we're emulating an outline using
text-shadow. To do this, add another four shadows, each offset
1px on all four diagonals:
Did you enjoy this tutorial? There's more to come on Motion.dev. To stay updated, sign up to the newsletter!