Just lately, a shopper requested me to create a bulging textual content impact. These are precisely the sorts of artistic challenges I dwell for. I explored a number of instructions, JavaScript options, SVG filters, however then I remembered the idea of 3D layered textual content. With a little bit of cleverness and a few superior CSS, I managed to get a end result I’m genuinely happy with.
Visually, it’s putting, and it’s additionally an ideal mission to study all types of useful CSS animation methods. From the basics of layering, by way of aspect indexing, to superior background-image methods. And sure, we’ll use a contact of JavaScript, however don’t fear about it proper now.
There’s a lot to discover right here, so this text is definitely the primary of a 3 half collection. On this chapter, we are going to deal with the core method. You’ll discover ways to construct the layered 3D textual content impact from scratch utilizing HTML and CSS. We’ll cowl construction, stacking, indexing, perspective, and easy methods to make all of it come collectively visually.
In chapter two, we are going to add motion. Animations, transitions, and intelligent visible variations that deliver the layers to life.
In chapter three, we are going to introduce JavaScript to observe the mouse place and construct a totally interactive model of the impact. This would be the full bulging textual content instance that impressed your complete collection.
3D Layered Textual content Article Sequence
- The Fundamentals (you might be right here!)
- Movement and Variations (coming August 20)
- Interactivity and Dynamism (coming August 22)
The Methodology
Earlier than we dive into the textual content, let’s discuss 3D. CSS truly lets you create some wild three-dimensional results. Belief me, I’ve finished it. It’s fairly simple to maneuver and place parts in a 3D area, and have full management over perspective. However there’s one factor CSS doesn’t give us: depth.
If I need to construct a dice, I can’t simply give a component a width, a peak, and a depth. There is no such thing as a depth, it doesn’t work that approach. To construct a dice or some other 3D construction in CSS, we’ve two important approaches: constructive and layered.
Constructive
The constructive methodology could be very highly effective, however can really feel a bit fiddly, with loads of transforms and cautious consideration to perspective. You are taking a bunch of flat parts and assemble them collectively, someplace between digital Lego bricks and origami. Both sides of the form will get its personal aspect, positioned and rotated exactly within the 3D area. Out of the blue, you may have a dice, a pyramid, or some other construction you need to create.
And the outcomes may be tremendous satisfying. There’s one thing distinctive about assembling 3D objects piece by piece, watching flat parts remodel into one thing with actual presence. The constructive methodology opens up a world the place you’ll be able to experiment, improvise, and invent new types. You might even, for instance, construct a cute robotic bouncing on a pogo stick.
Layered
However right here we’re going to deal with the layered methodology. This method isn’t about constructing a 3D object out of sides or polygons. As a substitute, it’s all about stacking a number of layers, typically dozens of them, and utilizing refined shifts in place and colour to create the phantasm of depth. You’re tricking the attention into seeing quantity and bulges the place there’s actually only a intelligent pile of flat parts.
This method is tremendous versatile. Consider a dice of sticky memo papers, however as a substitute of squares, the papers are minimize to form your design. It’s excellent for textual content, 3D shapes, and UI parts, particularly with spherical edges, and you’ll push it so far as your creativity (and endurance) will take you.
Accessibility word: Understand that this methodology can simply develop into a nightmare for display screen reader customers, particularly when utilized to textual content. Be certain to wrap all further and ornamental layers with aria-hidden="true". That approach, your artistic results gained’t intrude with accessibility and be certain that folks utilizing assistive applied sciences can nonetheless have an excellent expertise.
Making a 3D Layered Textual content
Let’s kick issues off with a fundamental static instance, utilizing “lorem ipsum” as a placeholder (be at liberty to make use of any textual content you need). We’ll begin with a easy container aspect with a category of .textual content. Inside, we’ll put the unique textual content in a span (it can assist later once we need to model this textual content individually from the layered copies), and one other div with a category of “layers” the place we’ll quickly add the person layers. (And don’t overlook the aria-hidden.)
<div class="textual content">
<span>Lorem ipsum</span>
<div class="layers" aria-hidden="true"></div>
</div>
Now that we’ve our wrapper in place, we are able to begin constructing out the layers themselves. In chapter three, we are going to see easy methods to construct the layers dynamically with JavaScript, however you’ll be able to generate them simply with a easy loop in your preprocessor (in case you are utilizing one), or simply add them manually within the code. Take a look at the professional tip under for a fast approach to try this. The essential factor is that we find yourself with one thing that appears like this.
<div class="layers" aria-hidden="true">
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<!-- ...Extra layers -->
</div>
Nice, now we’ve our layers, however they’re nonetheless empty. Earlier than we add any content material, let’s rapidly cowl easy methods to assign their indexes.
Indexing the layers
Indexing merely means assigning every layer a variable (let’s name it --i) that holds its index. So, the primary layer will get --i: 1;, the second will get --i: 2;, and so forth. We’ll use these numbers in a while as values for calculating every layer’s place and look.
There are a few methods so as to add these variables to your layers. You possibly can outline the worth for every layer utilizing :nth-child in CSS, (once more, a easy loop in your preprocessor, should you’re utilizing one), or you are able to do it inline, giving every layer aspect a model attribute with the correct --i worth.
.layer {
&:nth-child(1): { --i: 1; }
&:nth-child(2): { --i: 2; }
&:nth-child(3): { --i: 3; }
/* ... Extra layers */
}
…or:
<div class="layers" aria-hidden="true">
<div class="layer" model="--i: 1;"></div>
<div class="layer" model="--i: 2;"></div>
<div class="layer" model="--i: 3;"></div>
<!-- ...Extra layers -->
</div>
On this instance, we are going to go together with the inline method. It provides us full management, retains issues simple to grasp, and avoids dependency between the markup and the stylesheet. It additionally makes the examples copy pleasant, which is nice if you wish to attempt issues out rapidly or tweak the markup instantly.
Professional tip: If you happen to’re working in an IDE with Emmet assist, you’ll be able to generate all of your layers directly by typing .layer*24[style="--i: $;"] and urgent Tab. The .layer is your class, *24 is the variety of parts, attributes go in sq. brackets [ ], and $ is the incrementing quantity. However, If you happen to’re studying this within the not-so-distant future, you may have the ability to use sibling-index() and never even want these methods. In that case, you gained’t want so as to add variables to your parts in any respect, simply swap out var(--i) for sibling-index() within the subsequent code examples.
Including Content material
Now allow us to discuss including content material to the layers. Every layer must comprise the unique textual content. There are just a few methods to do that. Within the subsequent chapter, we are going to see easy methods to deal with this with JavaScript, however in case you are on the lookout for a CSS-only dynamic answer, you’ll be able to add the textual content because the content material of one of many layer’s pseudo parts. This fashion, you solely must outline the textual content in a single variable, which makes it a terrific match for titles, brief labels, or something which may change dynamically.
.layer {
--text: "Lorem ipsum";
&::earlier than {
content material: var(--text);
}
}
The draw back, in fact, is that we’re creating additional parts, and I personally favor to avoid wasting pseudo parts for ornamental functions, just like the border impact we noticed earlier. We’ll have a look at extra examples of that within the subsequent chapter.
A greater, extra simple method is to easily place the textual content inside every layer. The draw back to this methodology is that if you wish to change the textual content, you’ll have to replace it in each single layer. However since on this case the instance is static and I don’t plan on altering the textual content, we are going to merely use Emmet, placing the textual content inside curly braces {}.
So, we are going to kind .layers*24[style="--i: $;"]{Lorem ipsum} and press Tab to generate the layers.
<div class="textual content">
Lorem ipsum
<div class="layers" aria-hidden="true">
<div class="layer" model="--i: 1;">Lorem ipsum</div>
<div class="layer" model="--i: 2;">Lorem ipsum</div>
<div class="layer" model="--i: 3;">Lorem ipsum</div>
<!-- ...Extra layers -->
</div>
</div>
Let’s Place
Now we are able to begin engaged on the styling and positioning. The very first thing we have to do is make sure that all of the layers are stacked in the identical place. There are just a few methods to do that as effectively , however I believe the simplest method is to make use of place: absolute with inset: 0 on the .layers and on every .layer, ensuring each layer matches the container’s measurement precisely. After all, we’ll set the container to place: relative so that every one the layers are positioned relative to it.
.textual content {
place: relative;
.layers, .layer {
place: absolute;
inset: 0;
}
}
Including Depth
Now comes the half that journeys some folks up, including perspective. To provide the textual content some depth, we’re going to maneuver every layer alongside the z-axis, and to truly see this impact, we have to add a little bit of perspective.
As with the whole lot thus far, there are just a few methods to do that. You might give perspective to every layer individually utilizing the perspective() perform, however my advice is at all times to use perspective on the dad or mum stage. Simply wrap the aspect (or parts) you need to deliver into the 3D world inside a wrapper div (right here I’m utilizing .scene) and apply the angle to that wrapper.
After setting the angle on the dad or mum, you’ll additionally want to make use of transform-style: preserve-3d; on every youngster of the .scene. With out this, browsers flatten all reworked youngsters right into a single aircraft, inflicting any z-axis motion to be ignored and the whole lot to look flat. Setting preserve-3d; ensures that every layer’s 3D place is maintained contained in the dad or mum’s 3D context, which is essential for the depth impact to come back by way of.
.scene {
perspective: 400px;
* {
transform-style: preserve-3d;
}
}
On this instance, I’m utilizing a reasonably low worth for the perspective, however you must undoubtedly mess around with it to fit your personal design. This worth represents the gap between the viewer and the thing, which instantly impacts how a lot depth we see within the reworked layers. A smaller worth creates a stronger, extra exaggerated 3D impact, whereas a bigger worth makes the scene seem flatter. This property is what lets us truly see the z-axis motion in motion.
Layer Separation
Now we are able to transfer the layers alongside the z-axis, and that is the place we begin utilizing the index values we outlined earlier. Let’s begin by defining two customized properties that we’ll use in a second: --layers-count, which holds the variety of layers, and --layer-offset, which is the spacing between every layer.
.textual content {
--layers-count: 24;
--layer-offset: 1px;
}
Now let’s set the translateZ worth for every layer. We have already got the layer’s index and the spacing between layers, so all we have to do is multiply them collectively contained in the remodel property.
.layer {
remodel: translateZ(calc(var(--i) * var(--layer-offset)));
}
This seems like an excellent second to cease and have a look at what we’ve thus far. We created the layers, stacked them on high of one another, added some content material, and moved them alongside the z-axis to offer them depth. And that is the place we’re at:
If you happen to actually attempt, and focus arduous sufficient, you may see one thing that type of appears to be like like 3D. However let’s be trustworthy, it doesn’t look good. To create an actual sense of depth, we have to usher in some colour, add a little bit of shadow, and possibly rotate issues a bit for a extra dynamic perspective.
Forging Shadows
Typically we would need (or want) to make use of the worth of --i as is, like within the final snippet, however for some calculations, it’s typically higher to normalize the worth. This implies dividing the index by the entire variety of layers, so we find yourself with a price that ranges from 0 to 1. By normalizing, we maintain our calculations versatile and proportional, so the impact stays balanced even when the variety of layers adjustments.
.layer {
--n: calc(var(--i) / var(--layers-count));
}
Now we are able to alter the colour for every layer, or extra exactly, the brightness of the colour. We’ll use the normalized worth on the ‘gentle’ of a easy HSL perform, and add a contact of saturation with a bluish hue.
.layer {
colour: hsl(200 30% calc(var(--n) * 100%));
}
Steadily altering the brightness between layers helps create a stronger sense of depth within the textual content. And with out it, you danger dropping among the finer particulars
Second, do not forget that we wrapped the unique textual content in a span so we might model it? Now’s the time to make use of it. Since this textual content sits on the underside layer, we need to give it a darker colour than the remainder. Black works effectively right here, and normally, though within the subsequent chapter we are going to have a look at examples the place it truly must be clear.
span {
colour: black;
text-shadow: 0 0 0.1em #003;
}
Ultimate Touches
Earlier than we wrap this up, allow us to change the font. That is in fact a matter of non-public style or model pointers. In my case, I’m going with a daring, chunky font that works effectively for a lot of the examples. It’s best to be at liberty to make use of no matter font suits your model.
Allow us to additionally add a slight rotation to the textual content, possibly on the x-axis, so the lettering seems at a greater angle:
.textual content {
font-family: Montserrat, sans-serif;
font-weight: 900;
remodel: rotateX(30deg);
}
And there you may have it, combining all the weather we’ve coated thus far: the layers, indexes, content material, perspective, positioning, and lighting. The result’s a phenomenal, three-dimensional textual content impact. It could be static for now, however we’ll deal with that quickly.
Wrapping Up
At this level, we’ve a stable 3D textual content impact constructed totally with HTML and CSS. We coated the whole lot from construction and indexing to layering, depth, and colour. It could nonetheless be static, however the basis is robust and prepared for extra.
Within the subsequent chapters, we’re going to flip issues up. We’ll add movement, introduce transitions, and discover artistic methods to push this impact additional. That is the place it actually begins to come back alive.
3D Layered Textual content Article Sequence
- The Fundamentals (you might be right here!)
- Movement and Variations (coming August 20)
- Interactivity and Dynamism (coming August 22)

