Creating CSS Shapes with Emoji

Avatar of Preethi
Preethi on

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’s background-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’s shape-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.