cssimageclip

Insert photo inside letters such as "O" with CSS


I am trying to insert photos into the empty space inside letters such as "O", "Q" using CSS so that it scales with the font size.

I am familiar with replacing the letter itself using background-image: url() and background-clip: text but that only gives me the photo in the stroke of the letters as below:

word with image replacing letter stroke


What I want to do is place the image within the "O" itself, so that the stroke of the letter forms a frame for the image as below:

word with image inserted into center of letter


As mentioned I am hoping it can be done with CSS so that everything scales together as the viewport changes. Is there any sort of clip or masking value that uses the inside of text for its border?

There will be six words in the "title" of which 3 hopefully will have images inserted. I considered SVG, but scaling as the viewport size changes and reflowing the line breaks could be an issue. So CSS clipping or masking seems better angle to explore.

Thanks


Solution

  • We can't access a glyph's sub path geometry (like the inner bowl of an "o" or zero).

    Workaround: inlined image in background

    But you can add an inlined image that's positioned behind the "o" and floating with the text:

    .w400 {
      font-weight: 400;
    }
    
    .w700 {
      font-weight: 700;
    }
    
    .open {
      font-family: 'Open Sans'
    }
    
    .montserrat {
      font-family: Montserrat;
    }
    
    h1 {
      font-size: 15vw;
      position: relative;
      color: rgba(0, 255, 0, 0.5);
    }
    
    .lh-1 {
      line-height: 1em;
    }
    
    .lh-1-2 {
      line-height: 1.2em;
    }
    
    .lh-1-5 {
      line-height: 1.5em;
    }
    
    
    .o-bg {
      /* basic vertical and horizontal position offsets */
      --o-width: 0.65ch;
      --top: calc( 0.5lh - 0.225ex);
      --left: calc(-0.85ch);
    
      border-radius: 0.5ch;
      display: inline-block;
      height: 0.9ex;
      object-fit: cover;
      position: absolute;
      z-index: -1;
      /* approximate "o" width derived from ch unit */
      width: calc(var(--o-width));
      transform: translateX(var(--left)) translateY(var(--top));
    }
    
    /* adjust positioning for other font-families */
    .montserrat .o-bg {
      --top: calc(0.5lh - 0.225ex);
      --left: calc(-0.8ch);
    }
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2family=Montserrat:wght@100;200;300;400;700;800;900&display=swap">
    
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
    
    <h3>line-height: 1em</h3>
    <h1 class="open w400 lh-1">
      Yo<img class="o-bg" src="https://picsum.photos/id/237/300/300" alt="pic">u <br>
      <span class="w700">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
      <span class="montserrat w400">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
      <span class="montserrat w700">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
    </h1>
    
    <h3>line-height: 1.5em</h3>
    <h1 class="open w400 lh-1-5">Yo<img class="o-bg" src="https://picsum.photos/id/237/300/300" alt="pic">u <br>
      <span class="w700">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
      <span class="montserrat w400">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
      <span class="montserrat w700">Yo<img class="o-bg" src="https://picsum.photos/id/237/150/150" alt="pic">u</span> <br>
    </h1>

    How it works

    We place the <img> element after the "o"

    position:absolute;
    z-index:-1;
    

    the image goes behind the letter and won't add any reflow/margins in the text block.

    Approximate font specific widths and heights

    We can't exactly set perfect offsets but we can approximate some dimension via font specific relative units:

    --o-width: 0.65ch;
    --top: calc( 0.5lh - 0.225ex);
    --left: calc(-0.85ch);
    

    ch describes the width of the 0 (zero) glyph – it's actual width will grow or shrink for wider or more condensed font-families. 0.65ch means we assume the letter "o" is ~ 65% in width relative to the number zero glyph. I works pretty well even for different font-weights and most sans-serif font-families.

    0.5lh defines an offset relative to the current line-height
    -0.225ex is relative to the fonts x-height and shift the element to the top. --left: calc(-0.85ch) we move the image 85% of its zer0-width to the left. Horizontal metrics are more tricky so we need to experiment with the values to find the best ones according to the fonts intrinsic tracking - some fonts have more space around glyphs others have a more compact letter-spacing. You also need to adjust the values if you change letter-spacing via CSS.

    There is no one-fits-all calculation we could apply via CSS calc.

    We need to adjust these values for different font-families according to their proportions (cap-height to x-height etc) However, once we found suitable values these should work across all weights and styles within this font-family.

    See also css tricks:"CSS Length Units"