0

In old newspapers, titles of articles would, if they stretched over three lines, often be centered like this (picture):

|RACKET KINGPINS       |
|    ARE ROUNDED UP    |
|       IN BOMB KILLING|

I.e. the alignment for the first line is left, second center, third right. Similarly, for titles that stretched over two lines, the first would be left-aligned and the second right-aligned. For one-liners, the title would be centered.

I've been trying to emulate this for a theme I'm working on but the problem is, how do I separate the lines from each other? The title for each post does of course not contain any actual hard-coded line breaks but break automatically according to the width constraints, so I can't just look for line breaks to split up the innerHTML of the DOM, for example.

So how would you go about doing this?

(I welcome a JavaScript solution, if that was unclear)

Marcus
  • 136
  • 2
  • 8
  • _“In old newspapers”_ … they knew beforehand, where the line breaks would be. And to implement that would not be that difficult. But if you want this to happen dynamically, there’s a couple of problems – the first of them obviously being, that with normally flowing text, the line breaks would not even _occur_ as you have them in your little example, because there is still enough space left on the first line for “ARE” to be on that line as well … – CBroe May 28 '14 at 03:51
  • [contd.] … so you would either have to insert line breaks beforehand, or have non-breaking spaces in between those “groups” of words, otherwise you would not _get_ this distribution of the words amongst three lines in that specific way in the first place. You can of course try and create something like this with JavaScript – for example try and split the text into three parts of roughly equal lenght based simply on pure number of characters, or more complex solutions that take into account character- resp. word width as well. – CBroe May 28 '14 at 03:53
  • @CBroe Thanks for the ideas about splitting into three parts based on number of characters. I don't agree that the fact that the line breaks would happen differently (unless the column was more narrow) in html is a problem - I mean, after all, there will still be line breaks. Even if it might not turn out as aesthetically pleasing as "hand-broken" text, it's no reason the alignment itself can't be done. – Marcus May 28 '14 at 04:13
  • Of course the line break would happen differently with normal text flow and the given element width – when we even consider just the simple case of a monospaced font as in the example in your question, as I said there is still space for “ARE” on the first line, and that would lead to `RACKET KINGPINS ARE` as first line, then the second one would become `ROUNDED UP IN BOMB` (because there is suddenly more space available, since the “ARE`' moved up to the first line), and the third line would have only the word `KILLING` on it … – CBroe May 28 '14 at 04:19
  • [contd.] … and now apply left-center-right alignment to that, and it doesn’t look that pleasing at all any more. – CBroe May 28 '14 at 04:19
  • Again, it might not be 100% as aesthethically pleasing in all cases - that's fine by me, I still want to find out how to do it. – Marcus May 28 '14 at 04:30
  • 1
    OK, if I was to implement something like this, I’d start by splitting the text at the white space, and putting each “word” (plus optional trailing punctuation characters like a comma or period) into a `span` element of its own (either via JS, or server-side already). Then (in the browser) loop through all those elements, and compare the `.offsetTop` value of the current one with that of the previous one – if they differ, the current element is obviously on the next line. Having gathered the information which words are on one “line”, it is easy to put them into additional container elements […] – CBroe May 28 '14 at 04:59
  • 1
    […], one container for each line, and apply different text-align values to them. One might even take that one step further, and use `.offsetWidth` to measure the actual width of each word, and try to apply some advanced mathemagic to see if a more ideal distribution of words over lines can be found (so that you don’t end up with let’s say two “long” lines and a third one with only one word on it). Additionally, if it’s gonna be more than three lines, one could also apply different padding-left values to the lines in the middle, so that centered text does not make them all look to similar … – CBroe May 28 '14 at 05:01
  • 1
    Thank you, was able to solve it this way! Might type my solution out and add it as an answer, if nobody else does. – Marcus May 28 '14 at 05:41

3 Answers3

1

Without JavaScript, you can't.

This is one of the limitations of HTML/CSS. If you need printing capabilities where you control exact position and size of text, use PDF.

Without JavaScript, you can't predict if a paragraph will span multiple lines and how much, simply because a user:

  • May not have the same fonts installed,

  • Or may have configured the text to appear bigger in his browser.

You can, instead:

  1. Add manually the line breaks in a way that in most situations, the width of every line will be inferior to the width of the container,

  2. Forbid line breaks (white-space: nowrap;) and:

  3. Align the three lines according to your needs.

With JavaScript, you may determine where to put line breaks by counting the actual number of lines, and then reducing the text character by character, until you get two lines, then one line, finding this way the exact locations where you may insert line breaks.

I consider it hackish enough, but, well, it's just my opinion. You may, on the other hand, care that changes to user configuration (such as text-only zoom like the one which was used in old browsers) once the page is loaded may break the layout.

Community
  • 1
  • 1
Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199
  • Don’t know, because your answer is correct in that this can’t be done – at least not in a reasonable way – with current CSS, it would need additional JS. – CBroe May 28 '14 at 03:47
  • You can actually do an indent, to do something more advanced than a simple indent you need to program a little more than the example I provided. – a coder May 28 '14 at 03:48
  • I didn't dowvote you so I couldn't say. I did come across a good way of counting the amount of lines though I can't tell whether it would be possible to also split them up that way: [link](http://stackoverflow.com/questions/783899/how-can-i-count-text-lines-inside-an-dom-element-can-i) – Marcus May 28 '14 at 03:48
  • @Marcus: that's what I called a JavaScript hack in my answer. – Arseni Mourzenko May 28 '14 at 03:50
  • @MainMa Why is it a hack? – Marcus May 28 '14 at 03:52
  • 1
    You can use javascript to solve the answer, so you can, not a hack, just code. – Andrew May 28 '14 at 03:54
  • @Marcus: because in order to know where to add breaks, you should start removing character by character, see if the height changed, and based on that, build the final text. I consider that hackish enough, but I'll edit my answer to add a note about that. – Arseni Mourzenko May 28 '14 at 03:56
  • Wouldn't there be other ways of determining the width? [Like this?](http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript) – Marcus May 28 '14 at 04:08
1

You could create a function to explode the string into an array of chars in javascript and then manipulate the array as needed.

You could make he manipulation easier by using a pr-made "word wrap" function on the string prior to exploding it, so that the title will automatically have \n characters inserted appropriately beforehand according to your max width specification.

Then after exploding you just need to wrap the sections in divs by inserting them in the array at the \n break points and use css classes to line them up as needed.

Then implode the array back into a string...

Andrew
  • 18,680
  • 13
  • 103
  • 118
1

You can try looping through all the characters, in each loop, select the current character into a range object, from that you can use getBoundingClientRect() method of the range object to get the bottom of the character rect. It's the way we detect the ending character on each line. Here is the signs to detect the ending character:

  1. If we use normal wrapping, all the lines should break at some space character. The special thing is that in this case, the ending space character will have rect (getting from getBoundingClientRect()) with both top and bottom being 0.
  2. If we use wrapping like word-break:break-all, the ending character may be some other character (not the space character). In this case we will detect the next character following the ending character, the next character will have bottom different from the ending character's bottom.

So it's fairly complicated indeed. In fact at first I thought we had only 1 case (the second case) but if we ignore the first case, we will have unexpected result. So firstly we need to check the first case, then the second case. Now detecting the ending character in each line helps us separate the whole text into separate lines. Then we can wrap each line in a div element with appropritate style set (to align the text in each line).

Here is the demo's code:

HTML:

<h3>Original column</h3>
<div class='o-column'>racket kingpins are rounded up in bomb killing</div>
<h3>Column after adjusted</h3>
<div class='column'>racket kingpins are rounded up in bomb killing</div>

CSS:

.column, .o-column {
  width:300px;  
  border:1px solid black;
  margin-bottom:5px;
  text-transform:uppercase;
  padding:5px;    
  font-size:30px;
  font-family:'Arial';
  word-wrap:break-word;
}

JS:

console.clear();
var column = document.querySelector('.column');
var ends = [];
var range = document.createRange();
range.selectNodeContents(column);
var lines = [];
var lastBottom = null;
//if the innerHTML has some \n character, there will be some unexpected
//behavior, all the \n characters should be removed first.
column.innerHTML = column.innerHTML.replace(/\n/g,'');
var len = column.innerHTML.length;
for(var i = 0; i < len; i++){    
  //set range for current character
  range.setStart(column.childNodes[0],i);
  range.setEnd(column.childNodes[0],i+1);
  var rect = range.getBoundingClientRect();    
  if((rect.bottom == 0 && rect.top == 0) ||
    rect.bottom != lastBottom && lastBottom != null || i == len-1){
    var line = document.createElement('div');
    var lineStart = ends.length ? ends[ends.length - 1] + 1 : 0;
    line.innerHTML = column.innerHTML.substring(lineStart, i == len - 1 ? len : i);
    lines.push(line);
    ends.push(rect.bottom ? i - 1 : i);    
  }
  if(rect.bottom != 0) lastBottom = rect.bottom;
  else lastBottom = null;
  if(ends.length > 3) break;
}

var n = lines.length;
//we align each line only if there are less than 4 lines
if(n < 4 && n > 0){
  column.innerHTML = "";    
  for(var i = 0; i < n; i++){
    column.appendChild(lines[i]);
  }
  if(n == 1) lines[0].style.textAlign = 'center';
  else {        
    lines[0].style.textAlign = 'left';        
    lines[n-1].style.textAlign = 'right';
    if(n == 3) lines[1].style.textAlign = 'center';
  }
}

Demo.

King King
  • 61,710
  • 16
  • 105
  • 130