Flex CSS with JavaScript for better reading

Posted
1:51 AM 12 October 2005
Updated
8:51 PM 29 May 2011

As a web designer with a print background I hate what happens to text on flexible (or fluid or jello or just sloppy!) web pages. It is impossible to set a line-height with CSS that will suit both a narrow and a wide formats of the page. Generally we have to compromise with something in the middle and hope that’s how most people will see it. Why not use a little JavaScript to make line spacing relative to line width?

Check out the demo page:
Presentational JavaScript to adjust text line-height in proportion to text column width.

Most of the content of this entry was extracted from my previous post Another NetNewsWire style. Visit that entry to download a copy of my latest NetNewsWire style which makes use of the JavaScript discussed on this page.

If you are wondering what line spacing has to do with anything - let me attempt to explain why before I explain how.

Line spacing is important

Where text content is the most important consideration, it is desirable to minimise inappropriate interruptions or distractions to the task of reading. Appropriate interruptions are elements of reading itself, for example: punctuation, new paragraphs or the contrast between a heading and the text around it. These things can all contribute to slow down reading, but used well, they serve to assist the reader’s comprehension of the text.

To read text that is, by obvious necessity, wrapped over multiple lines, your eyes must skip back quickly at the end of each line to the start of the next line. Common sense tells us that longer lines of text will minimise the frequency of this interruption. Trouble is, the more characters in each line the further this leap becomes. Consequently the more care and time your eyes must take to ensure you don’t skip back to the incorrect line. Either way, making the text column too wide or too narrow will interfere with smooth reading. Of course, like most things, the answer lies in a happy place between these two extremes.

If you can’t control the text width the next best thing you can do to compensate for an overly wide text measure is to increase the leading. You may call it line spacing or, if you know your CSS, line-height. This clears the path along which your eye travels when seeking the next line to read. Again, all things in moderation, more is not necessarily better. If excessively deep, this space will create a barrier between the lines rather than just a separation.

On a printed document, a typographer will typically take great care to get the balance right between these factors. To make the same decisions for text displayed on a computer involves more compromises - the same control is simply not available. Typically when you change the width of a column of text on your computer only the width is changed; rewrapping the text onto shorter or longer lines. Your software normally doesn’t include any mechanism to make compensating changes to any other text attributes, such as lines spacing. Perhaps it should!

Extending CSS with presentational JavaScript

A bunch of CSS properties already allow us to make properties of one element relative to another. For example dimensions and font sizes can be relative to the users font size; Widths, heights and positions may be relative to those of a parent element. Unfortunately there is no means to create new relationships between properties that the designers of CSS didn’t anticipate us wanting. Fortunately JavaScript provides a means for us to do just that.

It was with this principle in mind that I found, with the help of Google, Progressive Layout. This JavaScript, which appears to reproduce the behaviour provided by the CSS properties of min-width and max-width, gave me the perfect framework to start with. Thank-you Alessandro!

Update: I have since rewritten the code. See source of the new version and further explanation below.

My first version of the JavaScript is something like this:

MakeLayout("lineflex",15,60,1.4,2.4);

function getEmWidth()
{
    return document.getElementById("emunit").offsetWidth;
}

function pxToEm(pxMeasure)
{
    return pxMeasure/getEmWidth();
}

function MakeLayout(id,minw,maxw,minlh,maxlh)
{
    if(document.getElementById)
    {
        SetWidth(id,minw,maxw,minlh,maxlh);
        window.onresize=function(){ SetWidth(id,minw,maxw,minlh,maxlh);}
    }
}

function SetWidth(id,a,b,lha,lhb)
{
    var w=pxToEm(document.getElementById("lineflex").offsetWidth);
    var unittype="em";
    if(w==0) return;
    var el=document.getElementById(id);
    var d=el.style;
    if(w==a) 
    {
        d.lineHeight=lha;
    }
    else if(w==b) 
    {
        d.lineHeight=lhb;
    }
    else
    {
        d.lineHeight=((lhb-lha)*((w-a)/(b-a))+lha)
    }
}

For it to work a few other elements need to be in place.

  • The page element that is being resized, containing the text, has the ID of ‘lineflex’.

  • Update: The demo now includes a simple DOM script that adds this otherwise useless <DIV> automatically.

    I have used ems instead of pixels so the effect of the script ‘scales’ appropriately if you have a larger or smaller font size set. To make the size of an em available to the JavaScript there is an otherwise functionless div in the HTML of the template with an ID of ‘emunit’. This div has it’s width pre-set to 1em with CSS.

  • The minimum and maximum widths, and corresponding line-heights are set in the function call ‘MakeLayout’ at the top of the script.

A few things I learnt from this project

  • NetNewsWire will not load an external JavaScript file with a regular script element. This is possibly a good thing.

  • JavaScript doesn’t appear to have access to either min-width or max-width as properties set on an HTML element. This could be annoying if you were attempting to adapt this script to work on a number of different elements with different width properties in a page.

  • Testing this my script in Firefox I noticed the line-height was not altered until I released the mouse after resizing the window.

  • I was delighted when Safari/WebKit applied the changes triggered by window.onresize on the fly. It’s so smooth you could be forgiven for not even noticing the shift.

  • Putting off the redesign of your blog is all too easy ;)

Where from here

Know some JavaScript? Help out by writing a script that applies this mechanism to a number of independent elements in a page. Wouldn’t hurt if the script worked when inserted in the head of the page; for standards compliance and all.

The time it took me to get the script to work is evidence enough for me to know I’m no JavaScript wizard, so if you think you can improve any aspect of it or have any other ideas about how it could be applied in the wild show me how!

The limitations of web based typography demands designers to let go of the control with which we are accustomed to in print based design but that’s no reason give it up without a fight!

Here’s a couple of links relevant to this subject:

Further thoughts

11 February 2006

After reading a couple of very informative chapters of DOMscripting I decided the code above could use some improvement—in terms of best practices and ease of use. See the updated example. Without looking at the source the example ought to look identical. Here’s an overview of the changes:

  • Included some tests to prevent possible JavaScript errors in older browsers.

  • The useless <DIV> in the HTML is no longer required. (Now added with DOM script magic).

  • Consolidated the configuarable variables to the top of the file.

  • Variable names are more meaningful for improved human friendliness.

  • Used addLoadEvent to help avoid conflicts with other scripts that trigger when the page is loaded.

As a test I have applied the script to the individual entry pages of this site, it may still need some more tweaking but so far so good.

While you are here, you may like to download my updated NetNewsWire style, Ollicle Flex. It makes use of the same line-height optimisation technique.

3 June 2007

I have completely rewritten this script as a jQuery plugin. See new post Auto line-height: a jQuery plugin for flexible layouts

Comments

  1. Commented
    1:51 AM 7 February 2006

    Sorry, this is just off the top of my head and you probably knew it already, but…

    JavaScript should have access to the min-width and max-width CSS properties. You might be forgetting that, when using JavaScript to access CSS properties that include a hyphen ‘-‘, you need to remove the hyphen and use camelCase instead, e.g.

    document.getElementById(‘geoff’).style.minWidth = ‘12em’;

  2. Dan
    Commented
    1:51 AM 8 February 2006

    Thanks for the great script! I’m excited to try it out.

  3. Commented
    1:51 AM 11 February 2006

    Paul, thanks for you input!

    Yes min-width and max-width can be set this way. Unfortunately, as far as I know, they cannot be read by JavaScript, unless:

    • They were set with JavaScript or
    • Applied directly to the element with the style attribute.

    It’s a damn shame that JavaScript cannot read the CSS in the same way.

    The min and max values in my script do not set minumum and maximum widths for the element. They only determine the widths within which the script will adjust the line-height.

    If the element is wider or narrower than these values it will simply apply the maximum and miniumum line-heights respectively.

    Of course it would make sense to assign a corresponding min and max in your CSS.

  4. Shane Houstein
    Commented
    1:51 AM 22 March 2006

    Cool! What an awseome application of javascript… I love it! Here’s an easy way to handle multiple divs: I’ve changed MakeLayout() so that you can call it repeatedly, and register as many text areas as you like. It all goes into an array, and then the reisize handler loops through the array. I haven’t tested this (sorry for being slack), but it should work ok.

    function MakeLayout(id,minw,maxw,minlh,maxlh)
    {
        if(document.getElementById)
        {
            flexTexts.push({id:id,minw:minw,maxw:maxw,minlh:minlh,maxlh:maxlh});
        }   
    }
    
    function SetWidths() 
    {
        for (var i=0; i&lt;flexTexts.length; i++) 
        {
            var t = flexTexts[i];
            SetWidth(t.id,t.minw,t.maxw,t.minlh,t.maxlh);
        }
    }
    
    flexTexts = new Array();
    window.onresize=function(){ SetWidths();}
    
  5. Commented
    1:51 AM 22 March 2006

    Champion Shane! Thanks

  6. Nathar Leichoz
    Commented
    1:51 AM 22 November 2006

    Yes, you can get the min-width value even if it wasn’t set with Javascript or on the element directly. Just use the window.getComputedStyle(elm,null).getPropertyValue(“min-width”) function for W3C compliant browsers and the elm.runtimeStyle.minWidth for IE.

Oliver Boermans is a design geek in Adelaide, South Australia: ollicle.com is a place for Ollie to rant, reflect and share. Feed

Previous entry
9 October 2005
Another NetNewsWire style
Next entry
12 January 2006
New year upgrade