A tutorial on how to create various types of (animated) fills and strokes for text using different techniques including CSS and SVG.

I like to think that the future is already here. We have already so many exciting possibilities in CSS and SVG that some time ago we could only dream about. For example, we now have many possible ways to create text with an animated fill!

About a year ago the article Techniques for creating textured text by Sara Soueidan was published here on Codrops. This wonderful article will tell you all about the different methods for creating textured text with some modern web techniques including canvas.

I’m very interested in this topic but from a different perspective. I have the following questions: Can the text fill be animated? Can we fill text with animated shadows or gradients? Or what about using videos for filling the text?

In this article I’m going to share my experiments and five ways to create patterned filled text. In three out of these five cases we will retain the ability to select the text. Of course we’d like to have that for every case but with some workarounds we can solve this problem, too.

For each of the techniques I’ll show the browser support, the ability to select text and, most importantly, the possibility to animate the fill (highly experimental).

I will not include all the code here, only the most important parts, but you can find everything inside the download files and play with it.

Let’s begin.

Attention: Some of these techniques are very experimental and won’t work in all browsers. Please see the browser support at the beginning of each section. Internet Explorer has very little support.

Method 1: Using background-clip: text

Chrome Supported

Firefox Not supported

Internet Explorer Not supported

Safari Supported

Opera Supported

Certainly, this is the most obvious way which comes to one’s mind, although background-clip: text is only supported in Webkit-based browsers for now and only with the prefix -webkit- .

Attention: demos from this section will only work in Chrome, Opera and Safari.

The markup looks as follows:

<div class="box-with-text"> Text </div>

And the CSS looks like this:

.box-with-text { background-image: url(IMAGE URL); -webkit-text-fill-color: transparent; -webkit-background-clip: text; }

Click on the image to open the live demo.

Text remains text, so we can select and copy it. The drawback is the lack of browser support. In Firefox or IE this demo will look like the following:

But we can use CSS gradients with the -webkit- prefix in order to remove the background in non-Webkit browsers:

.box-with-text { background-image: -webkit-linear-gradient(transparent, transparent), url(IMAGE URL); -webkit-text-fill-color: transparent; -webkit-background-clip: text; }

Non-supporting browsers will simply ignore the entire string and we can avoid showing the background image altogether.

Demo 2. If you’ll look at this demo in Firefox or IE, you’ll see just white text on black background.





This simple trick with a gradient will allow us to fix the view in non-Webkit browsers if we don’t want to show an image but I believe that a workaround like this makes this method not a good choice for textured text yet.

If we want to animate the fill for example, CSS backgrounds don’t give us many possibilities. We can only animate the background position and size, but we can’t smoothly change colors.

Demo 3: use gradient and animate background position.







Check out a live demo:

Conclusion

Selectable text Yes Animatable fill Yes, but with restrictions Browsers support Webkit only

The technique using background-clip: text is not cross-browser and it only has limited possibilities for animating the fill.

Method 2: SVG text

Chrome Supported

Firefox Supported

Internet Explorer Supported

Safari Supported

Opera Supported

SVG is a wonderful format which has good browser support. With SVG we have three ways to make patterned text:

fill

mask

clip-path

I’ll cover all three techniques here, so let’s begin with using fill .

If you are not familiar with SVG, I recommend reading the SVG specification, the articles by Sara Soueidan or the SVG tutorials on WebPlatform.org.

This is how we can use text in an SVG:

<svg viewBox="0 0 600 300"> <text text-anchor="middle" x="50%" y="50%" dy=".35em" class="text" > Text </text> </svg>

And it’s regular text, we can select and copy it. Have a look at the result:

Text in SVG has the same properties as any other shape. It can have a fill and a stroke, and many other properties, such as being solid, have gradients or patterns.

Demo 5: SVG text with gradient fill.





The markup for a simple gradient:

<linearGradient id="gr-simple" x1="0" y1="0" x2="100%" y2="100%"> <stop stop-color="hsl(50, 100%, 70%)" offset="10%"/> <stop stop-color="hsl(320, 100%, 50%)" offset="90%"/> </linearGradient>

For learning more about creating gradients you can read the article Getting Started with SVG Gradients by Joni Trythall.

The fill of an element can be added with the attribute fill . When using gradients or patterns the ID must be defined inside the url() :

<svg viewBox="0 0 600 300"> <text text-anchor="middle" x="50%" y="50%" dy=".35em" class="text" fill="url(#gr-simple)"> Text </text> </svg>

Another approach is to define the fill in CSS:

.text { fill: url(#gr-simple); }

We can set the gradient colors in CSS, but we need to use SMIL to gain control over other properties of the gradient:

<!-- Gradient --> <radialGradient id="gr-radial" cx="50%" cy="50%" r="70%"> <!-- Animation for radius of gradient --> <animate attributeName="r" values="0%;150%;100%;0%" dur="5s" repeatCount="indefinite" /> <!-- Animation for colors of stop-color --> <stop stop-color="#FFF" offset="0"> <animate attributeName="stop-color" values="#333;#FFF;#FFF;#333" dur="5s" repeatCount="indefinite" /> </stop> <stop stop-color="rgba(55,55,55,0)" offset="100%"/> </radialGradient>

Demo 6: animated SVG gradient for a fading text effect







Take a look at the live demo:

For more on using SMIL read the article A Guide to SVG Animations (SMIL) by Sara Soueidan.

Now let’s have a look at patterns. Patterns in SVG can contain all types of SVG shapes and images. Patterns can be either simple or quite complex. SVG patterns can be resized without losing their sharpness.

Demo 7: SVG text with pattern





And the markup for the circles pattern looks as follows:

<pattern id="p-spots" viewBox="0 0 80 80" patternUnits="userSpaceOnUse" width="60" height="60" x="5" y="5" > <g class="g-spots"> <circle r="5" cx="10" cy="10"/> <circle r="7" cx="30" cy="30"/> <circle r="5" cx="50" cy="10"/> <circle r="9" cx="70" cy="30"/> <circle r="11" cx="50" cy="50"/> <circle r="5" cx="10" cy="50"/> <circle r="7" cx="30" cy="70"/> <circle r="9" cx="70" cy="70"/> </g> </pattern>

Only the position and size of the circles is defined, their colors are defined in CSS, or actually Sass, because it allows us to use iteration:

$colors: #1D4259, #0A7373, #30BF7C, #BAF266, #EEF272; $max: length($colors); .g-spots circle { @for $item from 1 through $max { &:nth-child(#{$max}n + #{$item}){ fill: nth($colors, $item); } } }

This results into the following CSS:

.g-spots circle:nth-child(5n + 1) { fill: #1D4259; } .g-spots circle:nth-child(5n + 2) { fill: #0A7373; } .g-spots circle:nth-child(5n + 3) { fill: #30BF7C; } .g-spots circle:nth-child(5n + 4) { fill: #BAF266; } .g-spots circle:nth-child(5n + 5) { fill: #EEF272; }

For more on SVG patterns you can read the Patterns article in the SVG Tutorial section on MDN.

When using patterns there are less options for animating the element than with ordinary SVG shapes. For example, transformations on shapes in patterns don’t work in Firefox. But we can animate the stroke in order to get something similar to scaling.

For this demo I use Sass too, but in this case I don’t only set the colors but also the animation-delay . This will animate the stars sequentially:

$colors: #551F7A, #BA2799, #D9587A, #FFDD00, #FFF3A1; $max: length($colors); $time: 2s; $time-step: $time/$max; .g-stars polygon { stroke-width: 0; animation: stroke $time infinite; @for $item from 1 through $max { &:nth-child(#{$max}n + #{$item}){ $color: nth($colors, $item); fill: $color; stroke: $color; animation-delay: -($time-step*$item); } } } /* Change stroke-width within animation */ @keyframes stroke { 50% { stroke-width: 10; } }

This results into the following CSS:

.g-stars polygon { stroke-width: 0; animation: stroke 2s infinite; } .g-stars polygon:nth-child(5n + 1) { fill: #551F7A; stroke: #551F7A; animation-delay: -0.4s; } .g-stars polygon:nth-child(5n + 2) { fill: #BA2799; stroke: #BA2799; animation-delay: -0.8s; } .g-stars polygon:nth-child(5n + 3) { fill: #D9587A; stroke: #D9587A; animation-delay: -1.2s; } .g-stars polygon:nth-child(5n + 4) { fill: #FFDD00; stroke: #FFDD00; animation-delay: -1.6s; } .g-stars polygon:nth-child(5n + 5) { fill: #FFF3A1; stroke: #FFF3A1; animation-delay: -2s; } /* Change stroke-width within animation */ @keyframes stroke { 50% { stroke-width: 10; } }

Demo 8: The stars don’t change their size but their stroke-width





If you look at the code of this demo, you’ll find that there are more shapes than expected. When we enlarge strokes, some of the shapes may be cut off by the bounds of the pattern. By using duplicated shapes we can solve this problem.

Have a look at the live demo:

As a fill we can also use animated GIFs. Usually, they are quite heavy, but they can be used for creating really nice effects:

For creating a fill with an image we use a pattern. The markup of the pattern with an image is the following:

<pattern id="p-fire" viewBox="30 100 186 200" patternUnits="userSpaceOnUse" width="216" height="200" x="-70" y="35" > <image xlink:href="http://yoksel.github.io/about-svg/assets/img/parts/fire.gif" width="256" height="300"/> </pattern>

Unlike background-clip: text this text will be correctly displayed in most modern browsers.

Have a look at the live demo:

Let’s talk about using a stroke. For HTML text we can create a cross-browser stroke using text-shadow but this type of stroke has pretty limited options:

Writing the CSS for this kind of stroke by hand can be very tedious, so I use a tiny function in Sass to do that. We can set the color and width as parameters. Despite of this convenient function, the resulting code will be quite big and cumbersome.

Unlike in HTML, strokes are available for all elements in SVG, including text. And besides being solid they can also be filled with gradients and patterns.

Demo 11: SVG text with animated stroke pattern







Check out the live demo:

We can also have a dashed stroke and animate it in different ways:

Demo 12: SVG text with animated dashed stroke pattern





For this type of stroke we need to duplicate the text as many times as colors are used. A better way to make this, is to use the symbol :

<svg viewBox="0 0 600 300"> <!-- Symbol --> <symbol id="s-text"> <text text-anchor="middle" x="50%" y="50%" dy=".35em"> Text </text> </symbol> <!-- Duplicate symbols --> <use xlink:href="#s-text" class="text" ></use> <use xlink:href="#s-text" class="text" ></use> <use xlink:href="#s-text" class="text" ></use> <use xlink:href="#s-text" class="text" ></use> <use xlink:href="#s-text" class="text" ></use> </svg>

And this is how we can control the colors and the animation:

$colors: #F2385A, #F5A503, #E9F1DF, #56D9CD, #3AA1BF; $max: length($colors); $dash: 70; $dash-gap: 10; $dash-space: $dash * ($max - 1) + $dash-gap * $max; $time: 6s; $time-step: $time/$max; .text { fill: none; stroke-width: 6; stroke-linejoin: round; stroke-dasharray: $dash $dash-space; stroke-dashoffset: 0; animation: stroke $time infinite linear; @for $item from 1 through $max { &:nth-child(#{$max}n + #{$item}){ $color: nth($colors, $item); stroke: $color; animation-delay: -($time-step * $item); } } } @keyframes stroke { 100% { stroke-dashoffset: -($dash + $dash-gap) * $max; } }

And this results into the following CSS:

.text { fill: none; stroke-width: 6; stroke-linejoin: round; stroke-dasharray: 70 330; stroke-dashoffset: 0; animation: stroke 6s infinite linear; } .text:nth-child(5n + 1) { stroke: #F2385A; animation-delay: -1.2s; } .text:nth-child(5n + 2) { stroke: #F5A503; animation-delay: -2.4s; } .text:nth-child(5n + 3) { stroke: #E9F1DF; animation-delay: -3.6s; } .text:nth-child(5n + 4) { stroke: #56D9CD; animation-delay: -4.8s; } .text:nth-child(5n + 5) { stroke: #3AA1BF; animation-delay: -6s; } @keyframes stroke { 100% { stroke-dashoffset: -400; } }

For each symbol we set an individual animation delay, so the parts of the stroke don’t accumulate in one place but spread through the contour of a letter.

Have a look at the live demo:

Conclusion

With this method the text can be styled using CSS. We can also select and copy the text. Another great advantage is that SVG has really good browser support. So for creating these kind of fills, SVG is one of the best candidates.

A disadvantage of using SVG is the restriction on the ability to manage the text. For example, the words in the text don’t break when the text meets the document boundaries. We can use some tricks to get line breaks, but in this case the text might simply break in the middle of a word. So this method is more appropriate for short texts and logos.

Selectable text Yes Animatable fill Yes, both fill and stroke can be animated, with some restrictions related to SVG animations. Browsers support All modern browsers

Method 3: SVG mask and SVG clippath

Chrome Supported

Firefox Supported

Internet Explorer Supported

Safari Supported

Opera Supported

There are two types of masks in SVG. We also have masks in CSS that can be applied to HTML elements, but browser support is not good enough, so let’s dive into SVG masks.

SVG masks can be applied to HTML elements too, but for now this feature only works in Firefox. SVG masks for SVG elements work fine in all modern browsers. And both clippath and mask can contain text.

What is the difference between mask and clippath ?

clippath simply cuts the object to its path boundaries. It’s like a scissor cut.

mask takes transparency and brightness of the mask into account which allows for a kind of filtering of the object.

Demo 13: SVG text with clippath





The markup for the clippath example:

<clippath id="cp-text"> <text text-anchor="middle" x="50%" y="50%" dy=".35em" class="text--line"> Text </text> </clippath>

The markup for the group of shapes to which we will apply the clip path:

<g clip-path="url(#cp-text)"> <circle r="70%" cx="300" cy="150" class="c-fill--color"/> <circle r="70%" cx="300" cy="150" class="c-fill"/> </g>

Depending on what you’d like to do, the circles can of course be replaced with any other shape. In this demo I use two circle elements where the second one is filled with a pattern, and the first one (visually behind the second one) is filled with a solid color. This is needed to hide the thin lines between pattern tiles in the second one. In simple cases only one shape can be used.

Demo 14: text with mask .





Masks can contains symbols, which is very convenient for text with rich effects, when we need several copies of the text.

<symbol id="s-text"> <text text-anchor="middle" x="50%" y="50%" dy=".35em" class="text" > Text </text> </symbol> <!--Mask--> <mask id="m-text" maskunits="userSpaceOnUse" maskcontentunits="userSpaceOnUse"> <use xlink:href="#s-text" class="text-mask" ></use> </mask>

The markup for the group will be the same but the mask is applied by using the attribute mask :

<g mask="url(#m-text)"> ... </g>

In both mask and clippath , the text can be styled, i.e. we can change the font family and font size. But the text can’t be selected.

This problem can be solved by duplicating the text from the mask/clippath, place it above the masked element and remove its fill by setting fill: transparent . Like that we’ll get a transparent text which can be selected and copied.

To duplicate the text it’s best to use symbol . But symbol doesn’t work inside a clippath , but if you use mask , you can use one symbol for the mask and one for the transparent text.

The copy of the symbol must be placed in the code below the element with mask or clip-path .

Try to select and copy the text in the following live demo:

It’s important to know that fill: none doesn’t work, but fill: transparent does. Also, a shape with a stroke but without a fill can be used to achieve the same effect. The text will then be selectable, too.

The great advantage of this technique is the variety of possibilities to animate the fill. You can use any combination of SVG elements and animate them in many different ways. In this case it’s possible to use transformations, animate the fill and the stroke.

Here we have some animated content in a group with clip-path and animated strokes in the pattern:

The next live demo shows a group of animated shapes clipped with clip-path :

There are more crazy ways to fill text. It’s possible to use HTML in SVG by using the foreignObject element, so it’s possible to apply a SVG mask/clippath to HTML elements with CSS effects and animations, even to video. This technique has some limitations, though. For example, playing videos ignore the mask in Chrome. So we can’t use this for now although it’s quite exciting. But we can do the same using other approaches.

Conclusion

Selectable text No, but we can make it accessible with a workaround Animatable fill Yes, it’s possible to animate both stroke and fill Browsers support All modern browsers.

In comparison to SVG text, this technique has more animation possibilities.

Method 4: Mix blend mode

Chrome Experimental flag

Firefox Supported

Internet Explorer Not supported

Safari Supported

Opera Experimental flag

Attention: in order to view the demos in this section, you’ll need to use Firefox or Safari, or in Chrome with the activated flag under chrome://flags/#enable-experimental-web-platform-features (relaunch required). To view the demos in Opera activate the flag opera://flags/#enable-experimental-web-platform-features and relaunch browser, too.

This method for making patterned text is very experimental and will not work in all browsers, but it is really interesting, because we are able to use anything we want as a fill for the text. Currently, browser support is not very good.

If you’re not familiar with blend modes, I recommend reading this article: Getting to Know CSS Blend Modes.

How it works: different blend modes work differently on the colors of the layer they are applied to. For example, the mode lighten will show only light areas of the content, dark areas will disappear. On the other hand, the mode darken will select dark areas, and light areas will disappear. This allows us to create a HTML layer with transparent areas of a complex shape, even in the shape of regular text which can be changed on the fly!

The following is a simplified version of the markup:

<div class="box-with-text"> <div class="text">Text</div> </div>

And this is the CSS:

.box-with-text { background: url(IMAGE URL) 50% 70% / cover; } .text { background: black; color: white; mix-blend-mode: darken; }

All the magic is contained in the line mix-blend-mode: darken . With this blend mode all white areas will become fully transparent and we can see the background of the parent.

The disadvantage of this technique is that we can’t cut off areas around the text. This way doesn’t allow us to put patterned text over another layer with a graphical fill (as it can be done using SVG masks). In fact, it will be just a holey layer through which we can see the underlying elements of the page. But under this layer we can place anything we can imagine, for example, animated CSS effects or even a video!

Demo 19 shows what can be done with animation and box-shadow:





There is a problem with thin lines appearing around the block of text, especially in Firefox. This can be fixed with a pseudo element which has a border in the color of the background. This will cover the thin lines:

.box-with-text { background: url(IMAGE URL) 50% 70%/cover; /* Hack to hide thin transparent lines while resizing objects */ } .box-with-text:after { content: ''; position: absolute; top: -2px; right: -2px; bottom: -2px; left: -2px; display: block; border: 4px solid black; } .text { background: black; color: white; mix-blend-mode: darken; }

The main disadvantage of this method is the lack of browser support, so for now this technique is more suitable for experiments rather than using it in real website projects.

Conclusion

Selectable text Yes Animatable fill Yes, and as fill any HTML content including video can be used Browsers support In Safari and Firefox blend modes are fully supported, in Chrome and Opera they are behind an experimental flag, IE doesn’t support them at all

Method 5: SVG mask and HTML

Chrome Supported

Firefox Supported

Internet Explorer Supported

Safari Supported

Opera Supported

This last technique is a little peculiar, too. It’s quite similar to the previous one, but it has better browser support, because yes — it’s SVG again!

I call this method the “reverse mask”. In its essence it consists in using a SVG mask to get a layer with a solid fill and a transparent area in the shape of text, so that, in the next step, this layer can be put over an HTML element with any content.

We won’t cut the extra bits of the layer with content (as we do when using mask or clippath ). On the contrary, we’ll make holes of the needed shapes in the layer with the solid fill, similar to a stencil. And this is how it looks:

Demo 20: Stencil text with an “inverted” SVG image mask





For this case, the SVG is divided into two parts. The first invisible part contains a symbol with the text and the mask:

<svg viewBox="0 0 600 300" class="svg-defs"> <!-- Symbol with text --> <symbol id="s-text"> <text text-anchor="middle" x="50%" y="50%" dy=".35em" class="text"> Text </text> </symbol> <!-- Mask with text --> <mask id="m-text" maskunits="userSpaceOnUse" maskcontentunits="userSpaceOnUse"> <rect width="100%" height="100%" class="mask__shape"/> <use xlink:href="#s-text" class="mask__text"/> </mask> </svg>

In simple cases you don’t have to add the symbol, but it helps make the code of the mask more readable. In more complex cases it will help you make some interesting effects.

In this case we need the mask inverted, that’s why it’s needed to set the white color for the shape which is filling the mask:

.mask__shape { fill: white; }

The color of text must be black because the transparent area in the mask must be in the shape of the text.

Also, we’ll need a more complex HTML structure:

<div class="box-with-text"> <!-- Content for text --> <div class="text-fill"></div> <!-- SVG to cover text fill --> <svg viewBox="0 0 600 300" class="svg-inverted-mask"> <!-- Big shape with hole in form of text --> <rect width="100%" height="100%" mask="url(#m-text)" class="shape--fill"/> <!-- Transparent copy of text to keep patterned text selectable --> <use xlink:href="#s-text" class="text--transparent"/> </svg> </div>

The lowermost layer contains the HTML element, to which we can apply a background or it can be filled with some HTML content. In simple cases the background can be applied to the parent container, but for more flexibility it’s better to use a separate element.

The next layer is the SVG which contains the shape with the fill and the mask. It’s a visible layer with a transparent area in the shape of the text. Above this layer we place a symbol with the text; this is needed to keep the text accessible.

The disadvantage of this technique is that the layer with the content is not cut off by the mask. The underlying layers can be seen through the transparent area of the topmost layer. We can’t cut off the text from the background and place it over other layers.

But this technique has some advantages, too. The first advantage is that HTML is placed outside the SVG and may contain anything we want: any animations and media content.

If you want to animate the fill of the text then that’s easy, too. But note that the performance is not the best in the following demo:

Would you like to fill the text with a video? No problem:

In these demos the managing of sizes might look a bit complicated. If you don’t want to resize the block with text, you won’t need that part of the code.

The second advantage, and very different from using mix-blend-mode , is that the fill of the layer with the mask doesn’t mix with underlying layers, so it can contain fills with gradients and patterns.

Conclusion

Selectable text No, but we can make it accessible Animatable fill Yes, any HTML content including video can be used as fill Browsers support All modern browsers

Final words

Now you know several techniques to create text with patterned fills, including animated ones. Even if we can’t use some of listed techniques right now, I’m sure that we will be able to use them in the future. And now, I think it’s time for you to start creating some impressive text effects for your next project 🙂

The download file contains all techniques included in this tutorial (animated and not animated text fills). If you prefer to have a look the demo showcase you can download the files here: ZIP file of demo showcase.