Placing text on top of an image requires careful attention to color and contrast to maintain legibility. Print designers have traditionally used partially opaque backgrounds to achieve this end, an effect easily replicated on the web using rgba()
or blend modes.
Another possibility is to blur the background, an effect often associated with television news broadcasts. On the web, might assume that placing a filtered <div>
over an image might do the trick, but that doesn’t work: CSS filters affect content inside the element they are applied to, not content underneath them. To render the effect successfully we’ll need to apply two copies of the same image: once as a background, and once as a real image. The base markup remains remarkably clean, being placed inside a <figure>
element:
<figure>
<img src="jellyfish.jpg" alt="Blue jellyfish">
<figcaption>
<strong>Turritopsis nutricula</strong> is a hydrozoan…
</figcaption>
</figure>
Then, the <figcaption>
is placed very precisely inside the element and hidden. font-size: 0
is used on the <figure>
to help ensure that there are no gaps inside the element, and that everything lines up precisely:
figure {
background: url(jellyfish.jpg);
background-size: cover;
position: relative;
color: #fff;
text-shadow: 2px 2px 1px rgba(0,0,0,0.6);
font-size: 0;
}
figcaption {
position: absolute;
top: 6%;
left: 18%;
width: 40%;
line-height: 1.4;
font-size: 1.6rem;
opacity: 0;
transition: .4s .2s opacity;
}
The image inside the <figure>
is scaled to match the background, and clipped to the same space occupied by the <figcaption>
. (Currently, this clipping effect will only work in Webkit-derived browsers: I’ll treat other browsers in a moment).
figure img {
width: 100%;
clip-path: polygon(15% 5%, 60% 5%, 60% 40%, 15% 40%);
transition: .6s filter;
}
On hover, the image and <figcaption>
are blurred and made visible, respectively:
figure:hover img {
filter: blur(8px);
}
figure:hover figcaption {
opacity: 1;
}
Making the Clip Responsive & Cross-Browser
The clip
effect will not react to text wrapping and growing longer as the viewport narrows, so we need to either make it large enough initially to contain that expansion, or modify it as it grows:
@media all and (max-width: 1200px) {
figure img {
clip-path: polygon(15% 5%, 60% 5%, 60% 60%, 15% 60%);
}
}
@media all and (max-width: 1000px) {
figure {
width: 100%;
}
}
@media all and (max-width: 600px) {
figcaption {
font-size: 1.2rem;
}
}
@media all and (max-width: 500px) {
figure img {
clip-path: polygon(15% 5%, 60% 5%, 60% 80%, 15% 80%);
}
}
At the same time, we have to use the older SVG technique for clipping the image for Firefox and other browsers that don’t yet support the new method. As I’ve demonstrated previously, we can use the same numbers, just divided by 100; thankfully, we can do in a single element:
<svg id="svgpath">
<defs>
<clipPath id="large" clipPathUnits="objectBoundingBox">
<polygon points=".15 .05,.6 .05,.6 .4,.15 .4" />
</clipPath>
<clipPath id="med" clipPathUnits="objectBoundingBox">
<polygon points=".15 .05,.6 .05,.6 .6,.15 .6" />
</clipPath>
<clipPath id="small" clipPathUnits="objectBoundingBox">
<polygon points=".15 .05,.6 .05,.6 .8,.15 .8" />
</clipPath>
</defs>
</svg>
Then reference each of the different id
s in the media queries:
figure img {
clip-path: url(#large);
}
@media all and (max-width: 1200px) {
figure img {
clip-path: url(#med);
}
}
To make sure the SVG does not introduce a gap in our page, it’s reduced to 0 dimension; the referenced clip-path
elements will still work:
svg#svgpath { width: 0; height: 0; }
The result is what you can see at the top of this article, or on CodePen.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/RPVzgB