35

Shrink wrapping a div to some text is pretty straightforward. But if the text wraps to a second line (or more) due to a max-width (as an example) then the size of the DIV does not shrink to the newly wrapped text. It is still expanded to the break point (the max-width value in this case), causing a fair amount of margin on the right side of the DIV. This is problematic when wanting to center this DIV so that the wrapped text appears centered. It will not because the DIV does not shrink to multiple lines of text that wrap. One solution is to use justified text, but that isn't always practical and the results can be hideous with large gaps between words.

I understand there's no solution to shrink the DIV to wrapped text in pure CSS. So my question is, how would one achieve this with Javascript?

This jsfiddle illustrates it: jsfiddle. The two words just barely wrap due to the max-width, yet the DIV does not then shrink to the newly wrapped text, leaving a nasty right-hand margin. I'd like to eliminate this and have the DIV resize to the wrapped text presumably using Javascript (since I don't believe a solution exists in pure CSS).

.shrunken {text-align: left; display: inline-block; font-size: 24px; background-color: #ddd; max-width: 130px;}

<div class="shrunken">Shrink Shrink</div>
onlinespending
  • 1,079
  • 2
  • 12
  • 20
  • 1
    some image demonstration or code sample (jsfiddle?) would be useful to understand and build on what you mean. – basarat Jan 30 '13 at 03:33
  • What's the problem that this doesn't solve? http://jsfiddle.net/uS6cf/1 – isherwood Jan 30 '13 at 03:38
  • why doesn't the div shrink when the text has to wrap (your max-width example)? just for the sake of more info on this question, how are you shrinking your div? – Hurricane Hamilton Jan 30 '13 at 03:43
  • @isherwood That actually illustrates the problem perfectly. The two words force a break, but the DIV is still at its max-width of 130px, while the largest word is less 130px, creating the margin on the left and right. I want the DIV to shrink to the largest wrapped line of text. Using 'Shrink Shrink' for the text in your example makes it more noticeable. – onlinespending Jan 30 '13 at 03:47

5 Answers5

8

It's not the prettiest solution but it should do the trick. The logic is to count the length of each word and use that to work out what the longest line is that will fit before being forced to wrap; then apply that width to the div. Fiddle here: http://jsfiddle.net/uS6cf/50/

Sample html...

<div class="wrapper">
    <div class="shrunken">testing testing</div>
</div>

<div class="wrapper">
    <div class="shrunken fixed">testing testing</div>
</div>

<div class="wrapper">
    <div class="shrunken">testing</div>
</div>

<div class="wrapper">
    <div class="shrunken fixed">testing</div>
</div>

<div class="wrapper">
    <div class="shrunken" >testing 123 testing </div>
</div>

<div class="wrapper">
    <div class="shrunken fixed" >testing 123 testing </div>
</div>

And the javacript (relying on jQuery)

$.fn.fixWidth = function () {
    $(this).each(function () {
        var el = $(this);
        // This function gets the length of some text
        // by adding a span to the container then getting it's length.
        var getLength = function (txt) {
            var span = new $("<span />");
            if (txt == ' ')
                span.html('&nbsp;');
            else
                span.text(txt);
            el.append(span);
            var len = span.width();
            span.remove();
            return len;
        };
        var words = el.text().split(' ');
        var lengthOfSpace = getLength(' ');
        var lengthOfLine = 0;
        var maxElementWidth = el.width();
        var maxLineLengthSoFar = 0;
        for (var i = 0; i < words.length; i++) {
            // Duplicate spaces will create empty entries.
            if (words[i] == '')
                continue;
            // Get the length of the current word
            var curWord = getLength(words[i]);
            // Determine if adding this word to the current line will make it break
            if ((lengthOfLine + (i == 0 ? 0 : lengthOfSpace) + curWord) > maxElementWidth) {
                // If it will, see if the line we've built is the longest so far
                if (lengthOfLine > maxLineLengthSoFar) {
                    maxLineLengthSoFar = lengthOfLine;
                    lengthOfLine = 0;
                }
            }
            else // No break yet, keep building the line
                lengthOfLine += (i == 0 ? 0 : lengthOfSpace) + curWord;
        }
        // If there are no line breaks maxLineLengthSoFar will be 0 still. 
        // In this case we don't actually need to set the width as the container 
        // will already be as small as possible.
        if (maxLineLengthSoFar != 0)
            el.css({ width: maxLineLengthSoFar + "px" });
    });
};

$(function () {
    $(".fixed").fixWidth();
});
SynXsiS
  • 1,860
  • 10
  • 12
  • 1
    I think in your case where wrapping occurs (`lengthOfLine = 0`) it needs to set it to the length of `curWord`. Otherwise that word is unaccounted for and it can throw off the wrapping and `maxLineLengthSoFar`. – xr280xr Oct 18 '16 at 17:00
  • 1
    Also, getLength appends txt to the existing content. If the span wraps (e.g. if txt's value is a hyphenated word) the width of the span then grows to accommodate its contents that are split between two lines. I tackled this by first removing the contents from el using var originalContents = el.contents().detach() then appending originalContents back to el after removing the span. I forked the fiddle showing these scenarios and a suggested fix for them: http://jsfiddle.net/t333d96f/2/ – xr280xr Oct 18 '16 at 22:45
  • 10
    Its 2021 - Is there by any chance a CSS soluton by now? I really do not want to have to do this with js – IARI Jan 28 '21 at 12:39
7

I little late, but I think this CSS code can be useful for other users with the same problem:

div {
  width: -moz-min-content;
  width: -webkit-min-content;
  width: min-content;
}
BernieSF
  • 1,722
  • 1
  • 28
  • 42
  • 11
    This causes wrapping of the content at each possible break point which is not the same effect. – xr280xr Oct 18 '16 at 00:04
  • I noticed the same as @xr280xr and then that adding both `{ width: min-content; min-width: 100%; }` let it shrink-wrap with minimal line breaks. – Chuck Waggon May 20 '22 at 03:44
0
const range = document.createRange();
const p = document.getElementById('good');
const text = p.childNodes[0];
range.setStartBefore(text);
range.setEndAfter(text);
const clientRect = range.getBoundingClientRect();
p.style.width = `${clientRect.width}px`;

p {
  max-width: 250px;
  padding: 10px;
  background-color: #eee;
  border: 1px solid #aaa;
}

#bad {
  background-color: #fbb;
}

<p id="bad">This box has a max width but also_too_much_padding.</p>
<p id="good">This box has a max width and the_right_amount_of_padding.</p>
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 17 '22 at 14:29
-1

I guess this is what you are thinking about, it can be done in css:

div {
    border: black solid thin;
    max-width: 100px;
    overflow: auto;
}

You can see it here: http://jsfiddle.net/5epS4/

Jon P
  • 826
  • 1
  • 7
  • 20
  • You just happened to pick a max-width that obscured the right margin as a result of the overflow. Using 200px shows it more so. – onlinespending Jan 30 '13 at 04:00
  • ahh... so you mean the parent div should also wrap to its child div? I think adding `display: inline-block;` on your wrapper div would solve your problem, if I do not misunderstood what you want to happen... Check it here http://jsfiddle.net/QrP4k/ – Jon P Jan 30 '13 at 04:21
  • 1
    Nope. I simply want the DIV that contains the text to shrink wrap to it even after it has wrapped the text due to the max-width. – onlinespending Jan 30 '13 at 04:27
-2

Try this: https://jsfiddle.net/9snc5gfx/1/

.shrunken {
   width: min-content;
   word-break: normal;
}
  • This just shrinks .shrunken to the size of the longest word, _asdaasdasdasd_, even though _asdaasdasdasd asd_ would fit in a container of size 200px, but with some extra unwanted padding horizontally. – Wolfzoon May 18 '22 at 11:10