Filed under CSS, JS

Line breaks with javascript

Over the years there have been numerous suggestions of different ways of doing line breaks on the web. Browser incompatibilities, lack of support for standardized features, and the creation of browser specific features all helps in making it incredibly difficult. Searching and reading for a while quickly brought me to think the best current solution is quirksmode’s wbr. It seems quite well supported but has two major flaws:

  1. <wbr> is not valid HTML
  2. The suggested solution does not work in Safari

So off I went to find a better one. I started trying some different combinations of spaces marked up with <span> tags and I tried some nifty CSS tricks before it hit me - this should be done with javascript. Javascript has wide browser support and after some research (and help from the people in EFNet #javascript) I actually got a working example up. I have been able to test it in FF 1.5/Win, IE 5.5/Win, IE 6/Win, Opera 8.51/Win+Mac, and Safari 2.0.3/Mac and everything seems to work fine in all of them.

How it works

The idea here is to add ordinary <br>s and using javascript to measure the length of the text. The user gives a piece of text and a width in pixels and the script returns the text with breaks inserted.

The hard part is to find at which letter we should break. My first idea was to just loop over the text and keep track of the width in pixels. When we get over the given width, back one step and add a break at that position. But this will be slow for small font-sizes and wide columns so let’s do something faster. Instead of looping through all letters we can use Binary Search (check the link if you’re not familiar with it, it’s good to know). This makes for a lot less calls.

We need a small change to the algorithm since we won’t be able to find the exact width we’re looking for (a letter is wider than 1px). Instead we break when we’re down to knowing that it’s between two letters. We then return the first letter’s position. Here is code:

function binSearch(text, searchLen) {
   var left = 0, right = text.length;
   var breakPos = left, lastBreakPos = right;
   while (Math.abs(lastBreakPos - breakPos) > 1) {
      lastBreakPos = breakPos;
      breakPos = Math.floor((left+right)/2);
      if (searchLen < getTextWidth(text.substring(0, breakPos)))
         right = breakPos - 1;
      else
         left = breakPos + 1;
   }
   return Math.min(breakPos, lastBreakPos);
}

The code above uses getTextWidth() to get the width of a piece of text. getTextWidth() makes use of the offsetWidth property to get the width of a piece of text. Problem is that only elements that are rendered have the property set. This means we need to add it to the page, measure it, and then remove it. This code does just that:

function getTextWidth(text) {
   var ea = document.createElement("span");
   ea.innerHTML = text;
   document.body.appendChild(ea);
   var len = ea.offsetWidth;
   document.body.removeChild(ea);
   return len;
}

Good. We now have two nice helper functions to handle all the hard work. Now we only need some code to actually split the text and add the breaks. We do this by using binSearch() to fetch where the next break should be. binSearch() returns an index and we split the line and add the first part (including a break) to a buffer. We then repeat the same procedure on the last part of the string. We keep splitting until getTextWidth() tells us we’re done.

function linebreak(text, maxLen) {
   var breakPos = 0;
   var out = "";
   var part1 = text, part2 = "";
   if (getTextWidth(part1) > maxLen) {
      while (getTextWidth(part1) > maxLen) {
         var breakPos = binSearch(part1, maxLen);
         part2 = part1.substring(breakPos, part1.length);
         part1 = part1.substring(0, breakPos);
         out += part1 + "<br>";
         part1 = part2;
      }
      return out + part2;
   }
   else
      return text;
}

It’s now time to use the function we have. The first line selects what elements we want to wrap. You can replace this with a loop if you want if you need to go over all the paragraphs in your document. The second line just fetches everything inside the paragraph and sends it to linebreak() for wrapping. The two lines are then made to trigger when the document is ready loading.

window.onload = function () {
   var e = document.getElementsByTagName("p")[0];
   e.innerHTML = linebreak(e.innerHTML, 200);
}

A working example is available that shows what it can do. So, what do you think? Could this be the best solution so far to handling line breaks?

Feel free to leave a comment, or subscribe to my feed.

related

List some related articles:

You might want to browse all articles.

linkback

These people have linked to this article:

  • No linkbacks yet

To get a link in this list: make sure your blogging software supports trackbacks or pingbacks and simply link to this article like this:

<a href="http://friendlybit.com/css/line-breaking-with-javascript/">Line breaks with javascript</a>

You can also trackback by copying this link, and pasting it into a trackback field in your blogging tool.

about

Author: Emil Stenström

Emil Stenström blogs about web development. Posts are bi-weekly.

To the about section

comments

What do you think about this article? I’d love to hear your view!

Scroll to comment box.

  1. Ed Everett 17 Feb

    01

    This is very nice, but I’d suggest it has one problem when used with real text:

    It seems to break any word that is at the end of the line irrespective of how long that word is. Ideally it should only break individual words that are too long.

    If it could cope leaving normal lines alone while breaking the words that are too long then I’d say it was perfect.

    By the way, do you mind if I use this on a site? (with appropriate credit of course).

  2. Emil Stenström 17 Feb

    02

    @Ed Everett: you are free to use it any way you want, with or without credit.

    Conserning your other problem, have you tried setting the width of the column in the CSS too?

  3. Chris 24 Mar

    03

    Emil,
    Interesting article. I used it as a basis when asked how to format poetry so lines that wrapped were right aligned.
    http://www.hftonline.com/forum/showthread.php?t=17015 Due to problems with innerWidth, I changed mine to use innerHeight. You don’t need to specify a required height, just check for when it increases due to wrapping. Thought you might be interested.
    Chris.

If you want to you can follow the disussion through a comment feed.

add comment

This is the place where you get to say your opinion about this article, use it well. Formating with HTML is allowed. A red asterisk means that the following field is required.

Comment Form




Comment Form