Flowing Text Around Images

Magazines, books, and other print layouts sometimes use images that overlap with the text, and have that text wrap around the shape of the image. It can make for an interesting effect, bringing a feeling of motion into a static medium. Done right, the text and image become conceptually closer in the mind of the viewer than having an image off to the side or otherwise positioned around the text. I use this on a couple of posts to show off artwork that’s related to the content, and I think it makes for a fun effect.

Traditionally this type of layout was limited to print because text that can re-flow as the medium changes sizes made it infeasible to manually add line breaks to shape the text around images.

With CSS Shapes, it’s not difficult to achieve text wrapping around an image by using the shape-outside: url() property. This directive will cause the browser to take the image’s outline and use it as the shape around which text will flow. Fortunately, shape-outside is well-supported in modern browsers and the fallback for unsupported browsers is to use a rectangle around the image as the shape, as if the image were simply floated off to the side (which it is!).

To get the desired effect, we’ll need to have an image with transparency (meaning that JPG images aren’t supported here). Typically I’ll find a PNG image and need to edit it in photo editing software such as Photoshop or Gimp to remove the (hopefully monochrome) background, but you can also use an image that already has transparency.

The image will be used both for the <img href> and for the shape-image: url() values. To take the image out of the normal flow of the page, we’ll need to float it either left or right. For selecting the images to style, I use the shape-image and one of shape-image-right or shape-image-left classed depending on what side of the page I’d like it to float.

The end result markup should look something like:

<img src="/image.png" style="shape-outside: url('/image.png');" alt="..." />

A Gazer from D&D looking down on and reading a levitating book. Art by Alex Mitchell.

Note that the shape-outside CSS directive goes right into the style attribute for the img tag. Because we don’t know ahead of time what image will go into each tag, this is really the only way to do this short of assigning custom classes to each shape image you add to your site. That doesn’t make sense for my use-case of a component that I use in a blog context - I don’t want to need to add a new CSS rule for each image. If your use case doesn’t involve reusing this type of behavior multiple times, it may make sense to go ahead and move that shape-outside directive into CSS.

To give the image some “breathing room”, you can add a shape-margin which will be used in addition to the shape-outside to give the image more space around it. I use .5 em which feels like a nice amount. Here’s a simplified version of the CSS I use. It’s probably small enough that it could all be put into the style attribute if you have bad taste. I’ve omitted a few other directives I use to position and size the image nicely, which you can find in the inspector if you’re interested.

Strangely, despite the documentation suggesting otherwise, I’ve found that in addition to the shape-margin, I need to add a margin-left and/or margin-right value at least as high as the shape-margin value. This seems to be true across at least Safari, Mobile Safari, Chrome, and Firefox. Larger values were ignored at least on Safari, but having the value at least as high as the shape-margin made the spacing work correctly on the “sharp edges” of images such as the point of a triangle or the corner of the book the Gazer is reading. Other parts of the image were correctly given margins without the extra directives.

img.shape-image {
  shape-margin: .5em;
}

img.shape-image.shape-image-left {
  float: left;
  margin-right: 0.5em;
}

img.shape-image.shape-image-right {
  float: right;
  margin-left: 0.5em;
}

To use this with Rails and the asset pipeline, you’ll need to use image_path to find the hashed image path. I also wrap the image in a link so that I can properly credit the artist.

# Post view
<%= render partial: "shape-image", src: ..., alt: ..., %>

# _shape-image.html.erb
<%= link_to_if local_assigns[:href], local_assigns[:href] do %%>
  <%= image_tag src,
    alt: local_assigns[:alt],
    title: local_assigns[:alt],
    style: "shape-outside: url('#{image_path src}');",
    G
    class: "shape-image shape-image-#{local_assigns.fetch(:position) { "left" }}"
  %>
<% end %>

I use a custom link_to_if that renders the block regardless of whether the condition evaluates truthy, as link_to would. The default Rails helper uses the block as a fallback for the link, which I don’t find useful.

I’ve had shape images since before rewriting this site in Rails, so I also have an example template for Jekyll:


# Post view
{% include shape-image.html src="..." href="..." position="..." alt="..." %}

# _includes/shape-image.html
{% if include.href %}<a href="{{include.href}}">{% endif %}
  <img
    class="shape-image shape-image-{{include.position}}"
    src="{{include.src}}"
    style="shape-outside: url('{{include.src}}');"
    alt="{{include.alt}}"
    title="{{include.alt}}"
  />
{% if include.href %}</a>{% endif %}

While writing up this article, a terrible thought occurred to me: GIF (and others) support both transparency and animation. Could this approach be used with an animated, transparent background GIF? The answer is yes, at least for Safari. Chrome and Firefox both use the first frame of the animation for their shapes, but the image continues to move. I can’t really think of any good reasons to do this anyhow, but here it is:

For the sake of those who don’t want to see animation or text moving all over the place, I’ve wrapped up this example in a details/summary tag.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

There’s an issue where both CSS and the <img> tag load the image, which causes animated images and the motion of the text to get out of sync. The result is that the text sometimes overlaps the image and it seems like a timing or performance problem, which it doesn’t appear to actually be. As this isn’t something I have spent much time on, but in my experience once the images have been cached, the problem disappears on subsequent page loads.

Adding images to articles and allowing text to flow around them makes for an immersive experience. While it used to be limited to print media, CSS Shapes allow us to use this on the web.