blog

Knockout text in CSS

A long time ago (in 2015.) I was working on a project that never saw the light of day. What I remember from that project is the design for 404 and 500 pages that required knockout text. The little twist was that text was filled with a colored version of the background image that was otherwise in displayed black and white.

Sadly, I don’t have screenshots of the original design but I did save the code snippet which, after light cleanup, still works!

Here is the final result:

See the Pen Knockout text by Teo Dragovic (@teodragovic) on CodePen.

I managed to refactor the code so it comes down to a single class. Here is the full code in Sass:

$image: 'beach.jpg'; // put path to the background image here

@mixin absolute($top: auto, $right: auto, $bottom: auto, $left: auto) {
    position: absolute;
    top: $top;
    right: $right;
    bottom: $bottom;
    left: $left;
}

.c-knockout-text {
    @include absolute(0, 0, 0, 0);
    padding: 20px;
    display: grid;
    place-content: center;
    text-align: center;

    font-size: calc(50px + 20vw);
    line-height: 0.8;
    font-weight: 900;
    text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);

    background-image: url($image);
    background-position: center top;
    background-repeat: no-repeat;
    background-size: cover;
    filter: contrast(160%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    // Fallback
    color: #0066a4;

    &:before {
        @include absolute(0,0,0,0);
        pointer-events: none;
        z-index: -1;
        content: "";
        background-image: url($image);
        background-position: center top;
        background-repeat: no-repeat;
        background-size: cover;
        filter: grayscale(100%);
        opacity: 20%;
    }
}

For the knockout effect I’m using combination of -webkit-background-clip: text; and -webkit-text-fill-color: transparentl. Already well documented technique.

The other part is using pseudo-element to position the second image exactly behind knocked out one (all background-* properties need to have same values) and use CSS grayscale filter to make it black and white. Using CSS filters I can modify the same image so I don’t have to load separate versions of the same resources. It’s a performance win.

I also used opacity, text-shadow and contrast filter to tweak contrast between background and foreground to my liking.