0

The following picture displays the goal of what i would like to accomplish:

The red boxes are added in post

The red boxes are added for illustrative purposes, and are not part of the actual website.

Three titles are to be lined up horizontally: Respectively the name of section (container [A]), a subsection (container [B]) and a subsubsection (container [C]) of a textbook. Some of these titles of very short, some very long. In this given case, all titles are quite long. I've set a max-width:70px; in CSS. This forces line-breaks as one would expect, but the final "actual width" of the text itself will not always equal 70px due to these line breaks:

enter image description here

Notice here the empty space in container [B] and [C]. This yields very ugly results when eventually removing the background:

enter image description here

Justification with text-align:justify is even worse (tried all various CSS options for the text-justify property):

enter image description here

This problem is not new. Two previous stackExchange posts cover a similar situation at link A and link B, dating back to 2015 and 2013 respectively. Both posts propose a solution, but both have problems, possibly due to their age. The one in link B seems to just calculate the non-shrinked width. The method described in link A works in theory, but not per say in practice:

The idea is simple. Use a loop to shrink the width one pixel at a time. Look for the width where the height changes, and use the width one pixel larger than that. In code, this could look like:

let shrinkToLineBreak = function(cont) {
    let oldH = cont.offsetHeight;
    let W = cont.offsetWidth;

    let h = oldH;
            
    while(h == oldH) {
         W--;
         cont.style.width = W.toString() + "px";
         h=cont.offsetHeight;
    }

    cont.style.width = (W+1).toString() + "px";
}

But there's a big problem: You can't really calculate the height immediately after changing the width. It takes time for the DOM and CSS to properly render. The naive solution then is to add a delay:

let shrinkToLineBreak = async function(cont) {
    let oldH = cont.offsetHeight;
    let W = cont.offsetWidth;

    let h = oldH;
            
    while(h == oldH) {
         W--;
         cont.style.width = W.toString() + "px";
         await new Promise(r => setTimeout(r, 100));
         h=cont.offsetHeight;
    }
    
    cont.style.width = (W+1).toString() + "px";
}

But this is not that great of a solution. What exact delay should we choose to be "safe"? The time required to get the proper height depends on a variety of factors. The processing speed of the device and the amount of content currently being loaded and formatted by the page likely factor in heavily, but even on an empty page like this with a beefy computer, i'm getting inconsistent results at up to and including 50ms. I've run into no problems testing at 100ms, but:

A: Who's to say that's safe? My testing isn't exactly extensive or universal.

B: 100ms sucks. It's visibly slow, and you can see when loading the page how each title needs time to shrink.

A less naive solution would be to somehow check when the element is ready to have it's height computed, but there appears to be no native eventListeners or states to determine this. window.addEventListener("DOMContentLoaded") does not register the change in height as a new call to "load" any HTML, and document.readyState remains at "complete" when changing the width.

Any help is appreciated!

Buster Bie
  • 121
  • 4

0 Answers0