How can I evenly balance text over multiple lines?
Asked Answered
S

8

33

I'd like to display a short piece of text that may wrap to two or three lines. It's inside a highly visual element and for styling purposes I'd like the lines to be as equal in length as possible, preferring one line over two.

Instead of this:

This is an example of a piece of text that will wrap to two

lines.

I want this:

This is an example of a piece of

text that will wrap to two lines.

But I don't want to limit the width of the text container:

This text is long enough to just barely fit in two lines. It

should not wrap to three or more lines so no width limiting.

Is there a way to do this in pure CSS or with a tolerable amount of JavaScript?

Subservience answered 19/1, 2016 at 11:33 Comment(5)
I don't think this is possible with css only. There need scripting like JavaScript/ JQuery.Ressler
How do you want to handle the case where there is a long word right on the point that it should wrap?Richly
Have you tried max-width on the text container ? What have you tried actually ? And why don't you want to limit the text container ?Schock
I am writng a simple javascript function . . . what is your plan about long word? should it be splited?Fossil
@Pogrindis: I haven't tried much yet since the only sure way I know of doing this would involve splitting the text to span elements, calculating their width and manually adding line breaks where needed on screen resize. I thought I'd ask first to see if there's a simpler solution. @Bit: No need for special handling for long words. Just note that the font is variable-width so we don't know how wide they'll end up being on the screen by text alone.Subservience
Y
26

The CSS Text Module 4 property:

text-wrap: balance;

will do this. https://drafts.csswg.org/css-text-4/#text-wrap

There's also a package which implements a polyfill for existing browsers.

Here's a demo of balance text. [Archived copy]

Yapok answered 25/10, 2016 at 9:59 Comment(2)
Until that becomes a thing, there's this... github.com/adobe-webplatform/balance-textLipski
Status. React alternative. Svelte alternative.Rabblerouser
R
3

If I understand what you're asking (and if I do, the general theme of 'use justify' doesn't quite do it), I don't think you'll be able to avoid writing a fair bit of JavaScript.

The only way I can see to do what you're asking would be to compare the width of the text to the width of the container, and adjust accordingly.

Formula: Width of a single line = width of text / roundUp(width of text/width of container)

i.e. (probably needs some adjustment to prevent it cutting words in half)

var widthOfText = functionToGetWidthOfText(width); //==120
var widthOfContainer = container.width(); //==100
var containerWidth = widthOfText / ceil(widthOfText/widthOfContainer);
// containerWidth = 120 / ceil(120/100) == 120 / 2 == 60
var formattingTextContainer.width = containerWidth
// text would go inside the formattingTextContainer, which would
// give you 2 lines of equal, 60px width

However, the issue with this is, making a "functionToGetWidthOfText". After some quick searching, all I have found is this stack overflow question where the answer is to...

Wrap text in a span and use jquery width()

i.e. put it in an element and measure how wide it is...you might be able to do that off-screen somewhere/delete it after fast enough no-one sees it, but that will probably take quite a few lines of JavaScript! (I might investigate this more after work...)

The reason you can't flat-out figure out the width without rendering it is due to the various font types and sizes that it could be represented with. So if you are always using an exact font-type and font-size, you might be able to write a function that does it without rendering it.

Rozanne answered 19/1, 2016 at 12:48 Comment(1)
Yes, looks like you understood my question correctly. :) Your pseudocode example looks like a reasonable starting point in case a simpler solution doesn't come up.Subservience
S
2

Since there seems to be no ready solutions available, I wrote a relatively simple jQuery function to do what I want. Here it is in case someone else is looking for one.

It works by cloning the element we want to adjust. The clone is hidden to prevent it from blinking when it is injected to the DOM. We then make the clone narrower one pixel at a time until it has to wrap. The width right before that happens is then set to the original element and the clone element is removed.

Better ways to achieve this are still welcome, as are suggestions to improve this one. Codepen is available here: http://codepen.io/Kaivosukeltaja/pen/jWYqZN

function adjust() {
  $('.quote').each(function() {
    // Create an invisible clone of the element
    var clone = $(this).clone().css({
      visibility: 'hidden',
      width: 'auto'
    }).appendTo($(this).parent());
    
    // Get the bigger width of the two elements to be our starting point
    var goodWidth = Math.max($(this).width(), $(clone).width());
    var testedWidth = goodWidth;
    var initialHeight = $(clone).height();

    // Make the clone narrower until it wraps again, causing height to increase
    while($(clone).height() == initialHeight && testedWidth > 0) {
      goodWidth = testedWidth;
      testedWidth--;
      $(clone).width(testedWidth);
    }

    // Set original element's width to last one before wrap
    $(this).width(goodWidth);
    
    // Remove the clone element
    $(clone).remove();
  });
}

$(window).resize(adjust);
$(document).ready(function() {
  adjust();
});
body {
  background-color: #f0f0f0;
}

.holder {
  text-align: center;
  margin: 2em auto;
}
.quote {
  display: inline-block;
  border: 1px solid #c0c0c0;
  background-color: #fff;
  padding: 1em;
  font-size: 40px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="holder">
  <div class="quote">
    If I want text to wrap, I want the lines as close to equal length as possible.
  </div>
</div>
Subservience answered 20/1, 2016 at 18:34 Comment(0)
C
1

I created an exmaple with text-align: justify and without text-align: justify.

Have a look at:

    .center {width:200px; float:left; margin-left: 20px;}
    .justify { text-align: justify; }
      
 
    <!-- 4. -->
    <section class="center">
      <p class="justify">orem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </section>
    <section class="center">
      <p class="">orem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </section>
Consumer answered 19/1, 2016 at 11:46 Comment(2)
Try removing everything in the first paragraph after the word consectetur. I'm expecting the lines to be close to equal length, but instead consectetur stays alone on its own line.Subservience
okay now i understand your problem. It's not that easy. I think its not possible with pure CSS. The reason is that CSS needs to calculate the middle of the string to cut it in half.Consumer
P
0

I am afraid there is no way of doing that in CSS so I just searched it and found it here split-large-string-in-n-size-chunks-in-javascript AND counting-words-in-string

I am going to take this as a challenge and write a function for it :)

try and regex the number of words and find the length of half of that then this:

function chunkString(str, length) {
  length = length/2;
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}

.....

text-align: justify. I am not sure if i got the question right but looking at your reputation I am scared to answer this!! :)

put the text in a p, or span, make sure it is display: block, inline-block will do the job as well. width of the each element should be 50% of the parent element. then text-align: justify the child element.

Phares answered 19/1, 2016 at 11:40 Comment(4)
I am thinking to provide a javascript function that will wrap this ... but #looking at his reputation am scared too.Fossil
hahahah, same here but this is such a simple thing in css that I wonder if I got the question right! :) text justify has been around for donkey's years!Phares
There is no point in being intimidated by a reputation, nobody knows everything.Schock
Exactly, there's zero need to be intimidated by reputation. I've seen people with way over 10k reputation ask very basic questions on topics they're not previously familiar with. Unfortunately I can't limit the text width, see my updated third example to see why.Subservience
C
0

You can put the text into a div and use both width and text-align css properties:

#center{
  width : 100px;
  text-align : justify;
}
<div id="center">This is an example of a piece of text that will wrap to two lines.This is an example of a piece of text that will wrap to two
lines.</div>
Comnenus answered 19/1, 2016 at 12:8 Comment(0)
U
-1

You can use a parent tag like DIV and style that with width=50%. That would made the text in more lines.

Ulrika answered 19/1, 2016 at 11:39 Comment(0)
D
-2

Just shorten the width of the element holding this text:

div {
    width: 210px;
}
<div>This is an example of a piece of text that will wrap to two lines.</div>
Decongestant answered 19/1, 2016 at 11:41 Comment(1)
See failing example - the lines should either be of equal width or on a single line: jsfiddle.net/cqmwqgbj/3Subservience

© 2022 - 2024 — McMap. All rights reserved.