I recently came across this dribbble/landing page concept with hollow/filled text.
First off I'm not entirely sure if this concept could be recreated in CSS. A bit of Google did lead me to CSS text masks, but I wasn't able to find any post that can really recreate this effect.
How would I be able to reconstruct the hollow/filled text, depending if the background behind the text has an image or not?
It can be done in pure-HTML+CSS (without any SVG).
I decided to do it for HTML+CSS because I felt like a challenge.
mask-image: element(#target)
which means we don't need the mask-only text, unfortunately Chrome doesn't seem to support element()
yet (but Firefox does, though).A minor wart is the text-stroke
outline does not exactly line-up with the solid white text (at least in Chrome on Windows 10 with my computer's version of Helvetica
at 96dpi, but at 192dpi (2x, aka Retina)) in the same browser and computer it looks perfect.
Here's how it looks at different points in the animation on my machine at 96dpi:
The example implementation below works in the following browsers (at the time of writing):
body {
background-color: #dbdac2;
--solid-white: linear-gradient(white,white);
}
#container,
#container > #div1 {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-size:
153px 302px,
148px 302px,
154px 302px;
background-position:
131px 94px,
309px 28px,
480px 94px;
background-repeat:
no-repeat,
no-repeat,
no-repeat;
animation: moveImages 2s infinite;
animation-direction: alternate;
}
#container {
border: 1px solid white;
position: relative;
width: 711px;
height: 440px;
/* These are the 3 photo images, rendered as separate background-image layers: */
background-image:
url( "https://i.sstatic.net/hmwyh.png" ),
url( "https://i.sstatic.net/JeHEg.png" ),
url( "https://i.sstatic.net/pVgz6.png" );
}
#container p {
margin: 0;
position: static;
padding-top: 192px;
padding-left: 62px;
overflow: hidden;
font-family: Helvetica;
font-size: 99px;
letter-spacing: -2px;
font-weight: 600;
}
#container > #pStroke {
text-stroke: 1px white;
-webkit-text-stroke: 1px white;
color: transparent;
}
#container > #div1 {
/* #div1's background-image layers must match #container's: */
background-image:
var(--solid-white),
var(--solid-white),
var(--solid-white);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
@keyframes moveImages {
/* The `@keyframes from {}` rule is optional, btw. */
to {
background-size:
53px 302px,
58px 302px,
154px 302px;
background-position:
431px 94px,
209px 28px,
280px 194px;
}
}
<div id="container">
<div id="div1">
<p id="pWhite">Fashion Give<br />Impression.</p>
</div>
<p id="pStroke">Fashion Give<br />Impression.</p>
</div>
Explanation:
The div#container
element has a background-image
property set to 3 different sources images (using meme images in lieu of the fashion photos from your posted example).
background-size
and background-position
properties.Another element (#div1
) overlays div#container
through absolute
positioning and has the same background-size
and background-position
properties, but uses a single solid white background image (from a linear-gradient(white,white)
) instead of the photos, and then those 3 white background image layers are masked by #div1
's inner <p>
's text using background-clip: text;
.
background-image: linear-gradient
(or background-image: url("1x1px-white.png");
) instead of background-color: white;
because it needs to be repeated in 3 separate layers, whereas background-color: white;
cannot be used to define a rectangular area within the background, nor can you have multiple background-color
layers (even when semi-transparent).#div1
's <p>
element is used to position the text correctly by using only its inner padding
instead of position: absolute;
because positioned text cannot be used with background-clip: text;
, unfortunately.Another <p>
element with a copy of the text is used for the stroked text (with text-stroke: 1px white;)
While the text content is duplicated in the HTML source, the more fiddly size and position information of the 3 images (and their respective white masks) does not need to be duplicated in the CSS, fortunately; thanks to how CSS's selectors work (as both #container
and #div1
have their background-size
and background-position
properties set by a single CSS rule).
Possible alternative approaches:
Instead of using a repeated identically sized background-image
layers for the solid white parts, a single (but horribly complicated) clip-path
tracing the 3 boxes (like a single line drawn like an etch-a-sketch) could be used on #pWhite
but this would not be feasibly animatable.
I think the best possible approach would be something like this:
<img />
elements for the 3 photos (instead of background-image
) and using absolute
positioning inside a new <div id="images">
.<p>
(in a sibling element to div#images
) would be absolute
-ly positioned over div#images
by z-index
and masked with mask-image: element(#images);
element()
in HTML+CSS, only Firefox does, as far as I know.<p>
element positioned behind the div#images
.<img/>
elements can be positioned using transform: translate
instead of having to go through background-position
or position: absolute
which would yield much better performance and framerates.I can't think of any approaches that don't require duplicating the text content, though - at least until CSS's content:
property allows elements to copying text from other elements.