CSS Shapes is a standard that lets us create geometric shapes over floated elements that cause the inline contents — usually text — around those elements to wrap along the specified shapes.
Such a shaped flow of text looks good in editorial designs or designs that work with text-heavy contents to add some visual relief from the chunks of text.
Here’s an example of CSS Shape in use:
The shape-outside
property specifies the shape of a float area using either one of the basic shape functions — circle()
, ellipse()
, polygon()
or inset()
— or an image, like this:
Inline content wraps along the right side of a left-floated element, and the left side of a right-floated element.
In this post, we’ll use the concept of CSS Shapes with emoji to create interesting text-wrapping effects. Images are rectangles. Many of the shapes we draw in CSS are also boxy or at least limited to standard shapes. Emoji, on the other hand, offers neat opportunities to break out of the box!
Here’s how we’ll do it: We’ll first create an image out of an emoji, and then float it and apply a CSS Shape to it.
I’ve already covered multiple ways to convert emojis to images in this post on creative background patterns. In that I said I wasn’t able to figure out how to use SVG <text>
to do the conversion, but I’ve figured it out now and will show you how in this post. You don’t need to have read that article for this one to make sense, but it’s there if you want to see it.
Let’s make an emoji image
The three steps we’re using to create an emoji image are:
- Create an emoji-shaped cutout in SVG
- Convert the SVG code to a DataURL by URL encoding and prefixing it with
data:image/svg+xml
- Use the DataURL as the
url()
value of an element’sbackground-image
.
Here’s the SVG code that creates the emoji shaped cutout:
<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'>
<clipPath id='emojiClipPath'>
<text x='0' y='130px' font-size='130px'>🦕</text>
</clipPath>
<text x='0' y='130px' font-size='130px' clip-path='url(#emojiClipPath)'>🦕</text>
</svg>
What’s happening here is we’re providing a <text>
element with an emoji character for a <clipPath>
. A clip path is an outline of a region to be kept visible when that clip path is applied to an element. In our code, that outline is the shape of the emoji character.
Then the emoji’s clip path is referenced by a <text>
element carrying the same emoji character, using its clip-path
property, creating a cutout in the shape of the emoji.
Now, we convert the SVG code to a DataURL. You can URL encode it by hand or use online tools (like this one!) that can do it for you.
Here’s the resulted DataURL, used as the url()
value for the background image of an .emoji
element in CSS:
.emoji {
background: url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px' font-size='130px'>🦕</text> </clipPath> <text x='0' y='130px' font-size='130px' clip-path='url(%23emojiClipPath)'>🦕</text></svg>");
}
If we were to stop here and give the .emoji
element dimensions, we’d see our character displayed as a background image:
Now let’s turn this into a CSS Shape
We can do this in two steps:
- Float the element with the emoji background
- Use the DataURL as the
url()
value for the element’sshape-outside
property
.emoji {
--image-url: url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px' font-size='130px'>🦕</text> </clipPath> <text x='0' y='130px' font-size='130px' clip-path='url(#emojiClipPath)'>🦕</text></svg>");
background: var(--image-url);
float: left;
height: 150px;
shape-outside: var(--image-url);
width: 150px;
margin-left: -6px;
}
We placed the DataURL in a custom property, --image-url
, so we can easily refer it in both the background
and the shape-outside
properties without repeating that big ol’ string of encoded SVG multiple times.
Now, any inline content near the floated .emoji
element will flow in the shape of the emoji. We can adjust things even further with margin
or shape-margin
to add space around the shape.
If you want a color-blocked emoji shape, you can do that by applying the clip path to a <rect>
element in the SVG:
<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'>
<clipPath id='emojiClipPath'>
<text x='0' y='130px' font-size='130px'>🦕</text>
</clipPath>
<rect x='0' y='0' fill='green' width='150px' height='150px' clip-path='url(#emojiClipPath)'/>
</svg>
The same technique will work with letters!
Just note that Firefox doesn’t always render the emoji shape. We can work around that by updating the SVG code.
<svg xmlns='http://www.w3.org/2000/svg' width='150px' height='150px'>
<foreignObject width='150px' height='150px'>
<div xmlns='http://www.w3.org/1999/xhtml' style='width:150px;height:150px;line-height:150px;text-align:center;color:transparent;text-shadow: 0 0 black;font-size:130px;'>🧗</div>
</foreignObject>
</svg>
This creates a block-colored emoji shape by making the emoji transparent and giving it text-shadow
with inline CSS. The <div>
containing the emoji and inline CSS style is then inserted into a <foreignObject>
element of SVG so the HTML <div>
code can be used inside the SVG namespace. The rest of the code in this technique is same as the last one.
Now we need to center the shape
Since CSS Shapes can only be applied to floated elements, the text flows either to the right or left of the element depending on which side it’s floated. To center the element and the shape, we’ll do the following:
- Split the emoji in half
- Float the left-half of the emoji to the right, and the right-half to the left
- Put both sides together!
One caveat to this strategy: if you’re using running sentences in the design, you’ll need to manually align the letters on both sides.
Here’s what we’re aiming to make:
First, we see the HTML for the left and right sides of the design. They are identical.
<div id="design">
<p id="leftSide">A C G T A <!-- more characters --> C G T A C G T A C G T <span class="emoji"></span>A C G <!-- more characters --> C G T </p>
<p id="rightSide">A C G T A <!-- more characters --> C G T A C G T A C G T <span class="emoji"></span>A C G <!-- more characters --> C G T </p>
</div>
p#leftSide
and p#rightSide
inside #design
are arranged side-by-side in a grid.
#design {
border-radius: 50%; /* A circle */
box-shadow: 6px 6px 20px silver;
display: grid;
grid: "1fr 1fr"; /* A grid with two columns */
overflow: hidden;
width: 400px; height: 400px;
}
Here’s the CSS for the emoji:
span.emoji {
filter: drop-shadow(15px 15px 5px green);
shape-margin: 10px;
width: 75px;
height: 150px;
}
/* Left half of the emoji */
p#leftSide>span.emoji {
--image-url:url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px' font-size='130px'>🦎</text> </clipPath> <rect x='0' y='0' width='150px' height='150px' clip-path='url(%23emojiClipPath)'/></svg>");
background-image: var(--image-url);
float: right;
shape-outside: var(--image-url);
}
/* Right half of the emoji */
p#rightSide>span.emoji {
--image-url:url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='-75px' y='130px' font-size='130px'>🦎</text> </clipPath> <rect x='0' y='0' width='150px' height='150px' clip-path='url(%23emojiClipPath)'/></svg>");
background-image: var(--image-url);
float: left;
shape-outside: var(--image-url);
}
The width of the <span>
elements that hold the emoji images (span.emoji
) is 75px whereas the width of the SVG emoji images is 150px. This automatically crops the image in half when displayed inside the spans.
On the right side of the design, with the left-floated emoji (p#rightSide>span.emoji
), we need to move the emoji halfway to the left to show the right-half, so the x
value in the <text>
in the DataURL is changed to 75px. That’s the only difference in the DataURLs from the left and right sides of the design.
Here’s that result once again:
That’s it! You can try the above method to center any CSS Shape as long as you can split the element up into two and put the halves back together with CSS.
The technique really is neat – I missed your previous article, so this just made me aware that
shape-outside
actually works with images. Thank you for that!There is a known bug in Safari that will make responsive resizing of the image fail when using the
<foreignObject>
workaround – for example, if you give the background image size inem
orvw
units, or thepx
size of the background image does not match the size of the SVG. So it’s either Safari or Firefox, but not both…For the designer/developer, there is a way to get at the original shape: they are available at the Noto project on Github. What you need to know is the Unicode codepoint of the emoji. In your first example, that is
U+1F995
. Just go tohttps://github.com/googlefonts/noto-emoji/blob/master/svg/emoji_u1f995.svg
and you have the source SVG element. It may not match the font-based emoji you see depending on the OS or browser. But you can download that file, include its source code directly and work from there. Then you can freely work with the grafic to recolor, draw variants, produce effects and have a broader browser and OS support.
Hello Preethi, in CSS there is a different writing of unicode not “🦕”! The right writing is “\1f995;”
This doesn’t seem to be working in Chrome ♂️