Search code examples
cssflexbox

How to prevent a flexbox from clipping the bottom element contained within it (automatically; without hardcoding a fixed amount of additional space)


Browser: Firefox v134.0.2

In the following example, the bottom part of the y letters in the word yesterday don't seem to be contained within the <rb> tag, as they should; these bottom parts intrude on the space of the element that immediately follows <rb> tag.

ruby {
  ruby-position: under;
}

ruby ruby {
  ruby-position: over;
}

ruby {
  background-color: rgb(100, 100, 100);
}

ruby rt {
  background-color: rgb(200, 227.5, 255);
}

ruby ruby {
  background-color: rgb(255, 200, 200);
}

ruby ruby rt {
  background-color: rgb(200, 255, 200);
}
<ruby style="font-size:180px;display:inline-flex;flex-direction:column;text-align:center;/*justify-content:space-evenly;*/align-items:center;">
<span>
    <ruby>昨<rp>(</rp><rt>キ</rt><rp>)</rp></ruby>
    <ruby>日<rp>(</rp><rt>ノウ</rt><rp>)</rp></ruby>
</span>
  <rt>yesterday</rt>
</ruby>
<br><div style="width:50%;margin-left:15px;font-size:40px;background-color:rgb(0,255,0);">the bottoms of the "y" letters intrude into this element</div>

the output

Can we somehow solve this problem without hard-coding an additional space of a specific size (which we can determine by trial and error) below the y letters (e.g. <rt style="padding-bottom:5px;">yesterday</rt>), so that it's taken care of automatically (as it should've been)?


Solution

  • Original solution

    I don't think this has anything to do with Flexbox, but rather with how the height of text is calculated. The problem seems to be that the font's descender is not being taken into account when calculating the element's content-size. This can be fixed by setting line-height: normal on the rt element (see this blog post by Vincent De Oliveria: Deep dive CSS: font metrics, line-height and vertical-align).

    Doing so gives us the following (in Firefox):

    Screenshot of 'yesterday' appearing fully inside the blue box.

    ruby {
      ruby-position: under;
    }
    
    ruby ruby {
      ruby-position: over;
    }
    
    ruby {
      background-color: rgb(100, 100, 100);
    }
    
    ruby rt {
      background-color: rgb(200, 227.5, 255);
      line-height: normal;
    }
    
    ruby ruby {
      background-color: rgb(255, 200, 200);
    }
    
    ruby ruby rt {
      background-color: rgb(200, 255, 200);
    }
    <ruby style="font-size:180px;display:inline-flex;flex-direction:column;text-align:center;/*justify-content:space-evenly;*/align-items:center;">
    <span>
        <ruby>昨<rp>(</rp><rt>キ</rt><rp>)</rp></ruby>
        <ruby>日<rp>(</rp><rt>ノウ</rt><rp>)</rp></ruby>
    </span>
      <rt>yesterday</rt>
    </ruby>
    <br><div style="width:50%;margin-left:15px;font-size:40px;background-color:rgb(0,255,0);">the bottoms of the "y" letters intrude into this element</div>

    The reason this is a problem in Firefox and not in Chrome is that Firefox's user agent stylesheet has a line that sets line-height: 1 specifically for rt and rtc elements.

    rt elements sticking out the top

    As you point out in your comment, the green rt elements are still poking out from the top of the ruby element. To answer your comment... mostly.

    This can be fixed in Firefox by setting line-height: 1 for the ruby elements:

    All ruby-related elements now fall within the content-area of their enclosing paragraph, and have no overlap.

    p {
      font-size: 80px;
      font-family: serif;
    }
    
    ruby {
      line-height: 1;
      
      display: inline-flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
    
      ruby-position: under;
    }
    
    ruby ruby {
      ruby-position: over;
      display: ruby; /* undo inline-flex */
    }
    
    ruby rt {
      line-height: normal;
    }
    
    /* colours: */
    
    ruby {
      background-color: rgb(100, 100, 100);
    }
    
    ruby rt {
      background-color: rgb(200, 227.5, 255);
    }
    
    ruby ruby {
      background-color: rgb(255, 200, 200);
    }
    
    ruby ruby rt {
      background-color: rgb(200, 255, 200);
    }
    
    p {
      background-color: rgb(225, 225, 225);
    }
    <p lang="ja">
      大阪さんに<ruby>
      <span>
          <ruby>昨<rp>(</rp><rt>キ</rt><rp>)</rp></ruby>
          <ruby>日<rp>(</rp><rt>ノウ</rt><rp>)</rp></ruby>
      </span>
      <rt>yesterday</rt>
      </ruby>会いました。
    </p>

    Unfortunately, this doesn't quite work in Chrome. Not only do the green rt elements still poke out the top, but they now also overlap with the tops of the ruby elements:

    Two overlaps are visible from Chrome.

    That said, Chrome seems to do a better job than Firefox in the first place when it comes to positioning ruby annotations: they are placed much closer to the base text, and it seems to be a bit better at considering the font family's inherent sizes. So it's entirely possible that this solution is satisfactory.

    I should also note that I have found that changing the language of the document/containing element from en to ja and vice versa can introduce even more layout quirks: YMMV on that front.

    Avoiding Flexbox

    Personally, I think it would be far less of a headache if you simply forwent the use of Flexbox entirely. You did not explain why you chose to use a Flexbox in the original post; in your comment to SyndRain's answer, you said that:

    1. kanji should be separated by a space;
    2. kana and kanji should be mutually centered, depending on which of the two of them were longer; and
    3. English annotation underneath should also be centered along with the whole ruby element.

    You mentioned that achieving the same layout without it would require introducing several more layers of <span>/<div> elements. In my testing, however, the default behaviour of ruby text seems to do what you want without any additional help (if you have more specific requirements, please feel free to expand on them in the comments, and I will update my answer again).

    Here's an example that uses ruby and rt exactly as shown in the HTML Standard:

    p {
      font-size: 60px;
      font-family: serif;
    }
    
    ruby {
      ruby-position: under;
    }
    
    ruby ruby {
      ruby-position: over;
    }
    <p lang="ja">
      大阪さんに<ruby><ruby>昨<rp>(</rp><rt>キ</rt><rp>)</rp></ruby><ruby>日<rp>(</rp><rt>ノウ</rt><rp>)</rp></ruby><rt lang="en">yesterday</ruby>会いました。あいうえお、かきくけこ。
    </p>

    The only thing this doesn't do out of the box is add a space between each kanji. I'm not entirely sure why you would want to do this, as it would be inconsistent with any surrounding Japanese writing (again, please expand in the comments if I'm misunderstanding your goal here); however, you can accomplish this fairly easily with a creative CSS selector:

    p {
      font-size: 60px;
      font-family: serif;
    }
    
    ruby {
      ruby-position: under;
    }
    
    ruby ruby {
      ruby-position: over;
    }
    
    /* Insert space between annotated kanji: */
    ruby ruby:not(:last-of-type)::after {
      content: "\0a"; /* nbsp */
    }
    <p lang="ja">
      大阪さんに<ruby><ruby>昨<rp>(</rp><rt>キ</rt><rp>)</rp></ruby><ruby>日<rp>(</rp><rt>ノウ</rt><rp>)</rp></ruby><rt lang="en">yesterday</ruby>会いました。あいうえお、かきくけこ。
    </p>

    Using a pseudo-element has the added benefit that the text no longer includes any actual whitespace when copy-pasting: it is purely visual.

    Hopefully I didn't misunderstand your intentions with the flexbox. I hope this answers your question. :)