Image Mask Text
An image that only shows through the letters of a heading and drifts slowly inside them: your photo in a CSS variable, the text stays real text.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. SVG has 30 effects, including 3 free. Explore the category →
3 usage examples



How it works
The .imt-text heading gets the image as its background (background-image:var(--imt-image)), framed with cover on the text box. background-clip:text (and its -webkit- version) cuts that background to the exact letter shapes, and -webkit-text-fill-color:transparent removes the normal fill: only the image remains, wherever there are letters.
The imtGlide animation moves the background position from --imt-from (50% 10%) to --imt-to (50% 90%) over 8 s, then back (alternate, ease-in-out): like a camera slowly tilting down the photo. With cover framing the image overflows the box in one direction only; for a wide heading that is the height, hence the vertical path by default.
Your image goes in the HTML, on the root: style="--imt-image:url('photo.jpg')". Left as is, a sunset over the sea drawn in SVG (embedded in the CSS, no file) fills the text. The text is weight 900 at 4rem (Inter when available, otherwise the system font), centred in a 280 × 180 px dark card that --imt-card, --imt-width and --imt-min-h can hide or stretch. CSS only, no JavaScript.
Accessibility
- Real text: the heading stays an
<h2>(or any element with theimt-textclass): read by screen readers, selectable, indexed. The image is only a background: if it carries information, also say it in nearby text. - Contrast: legibility depends on the photo colours under the letters. A pale sky area on a light page, or a shadow on a dark one, erases parts of letters. Set the
--imt-from/--imt-topath to stay in a contrasted area, and use a very heavy font. - Reduced motion: under
prefers-reduced-motion: reducethe animation is removed and the image settles in the centre (50% 50%). - Fallback: should
background-clip:textbe unsupported, the text shows in light violet (color:#c9a3f5) instead of vanishing.
Browser compatibility
background-clip:text (prefixed -webkit- for Chrome before 120 and Safari before 14), -webkit-text-fill-color, CSS variables inside @keyframes, SVG data-URI image. Measured in Chromium, Firefox and WebKit (Playwright): image visible in the letters and only there (hiding the heading removes the coloured pixels), glide, two copies, reduced-motion stop, 390 px. The image fill works in older versions (Chrome 49, Firefox 49, Safari 9.1); the versions below also honour reduced motion.
Without clipping support the text stays readable in light violet. If the image fails to load the heading turns transparent: keep the image of your main heading on the same domain as the page.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0621-root">
<div class="imt-card">
<h2 class="imt-text">MASK</h2>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Image (CSS — --imt-image) |
sunset over the sea (embedded SVG) | url('photo.jpg') on the root, in the HTML (style) or your CSS. A bright, contrasted photo gives the most legible letters. |
Text size (CSS — --imt-size) |
4rem | The bigger and bolder the text, the more recognisable the image. |
Framing (CSS — --imt-fit) |
cover | Background size. 160% auto enlarges the image and leaves room for a horizontal path. |
Glide path (CSS — --imt-from, --imt-to) |
50% 10% → 50% 90% | Start and end positions of the image, back and forth. Two equal values stop the motion on that framing. |
One-way duration (CSS — --imt-speed) |
8s | Longer = subtler motion; under 4 s the image races behind the text. |
Card (CSS — --imt-card, --imt-width, --imt-min-h) |
#12121a / 280px / 180px | transparent, 100% and 0px remove the card: the heading sits straight in your layout. |
Block background and height (CSS — --imt-page, --imt-height) |
#0a0a0f / 300px | transparent and 0px to fit the effect into an existing section. |
FAQ
<div class="fx-0621-root" style="--imt-image:url('island.jpg')">. The image is framed with cover on the text box: a 1,200 px wide photo is enough for a full-width heading. Prefer an image on your own domain that loads fast: until it arrives the heading is transparent.--imt-from and --imt-to to a contrasted area, or set --imt-card to a card of the opposite colour.root.style.setProperty('--imt-image', 'url(pattern.png)') swaps the image instantly without touching the text. To mirror a real button, read its image with getComputedStyle(button).backgroundImage and pass it as is.