olivier
olivier

Reputation: 1017

Prefer new lines to long lines

I would like to control the way a text is displayed in box, preferring new lines to long lines, but still allowing long lines.

Here's some examples: http://codepen.io/anon/pen/jiCxo

enter image description here

I would like:

Also, I would like the first line to be the shortest, not the last one: http://codepen.io/anon/pen/irFcK.

Any ideas?

(if, as I fear, it is not possible using only CSS, I am open to a nice JavaScript solution)

Upvotes: 2

Views: 353

Answers (5)

olivier
olivier

Reputation: 1017

Inspired by TMP's answer and this answer to another question, I came up with this script that works great in most situations but still has some quirks…

var minimum_gap = 5; // Minimum gap on the last line to try to put it on the first.

$(".box h1").each(function() {
    var h1 = $(this),
        text = h1.text(),
        words = text.split(' '),
        lines = [],
        first_word = [],
        l = 0,
        i = 0;

    // Adding a span to measure the width
    h1.wrapInner('<span>');
    var span = h1.find('span');

    // Put the first word in the span and save the height
    span.text(words[0]);
    var height = h1.height();

    // Measure width of each line
    for(var i = 1; i < words.length; i++){
        span.append(' ' + words[i]);

        // If there's a new line
        if(h1.height() > height){
            lines[l] = span.width();
            span.text(words[i]);
            l++;
        }
    }
    // Last line
    lines[l] = span.width();

    var nb_lines = lines.length;
        ideal_line_width = 0;

    if(nb_lines == 1) {
        // If the line is "long", make it two
        if(span.width() >= h1.parent().width()/2) {
            ideal_line_width = h1.width()/2;
            nb_lines = 2;
        }
    }
    else {
        // Compute the average lines width (except for the last one)
        var sum = 0;
        for(l=0;l<(nb_lines-1);l++) {
            sum += lines[l];
        }
        var avg_width = sum/(nb_lines-1);

        // If last line is shorter than the average
        if(lines[nb_lines-1] < avg_width) {
            var gap = avg_width - lines[nb_lines-1];

            // Spread the gap among the lines
            gap /= nb_lines;

            if(gap > minimum_gap) {
                ideal_line_width = avg_width - gap;
            }
        }
    }

    // Let's make the wanted adjustments
    if(ideal_line_width != 0) {

        // Determining which is the first word of each line, beginning at the end in order to get the shortest one first
        l = nb_lines-1;
        span.empty();
        for(i=words.length-1;i>=0;i--) {
            span.prepend(words[i] + ' ');
            if(span.width() > ideal_line_width) {
                // If there's a new line, we cancel the last word
                if(h1.height() > height) {
                    i++;
                }
                span.empty();
                first_word[l] = i;
                l--;
            }
        }

        // Applying the results
        span.remove();
        l = 1;
        for(i=0;i<words.length;i++) {
            h1.append(' ' + words[i]);
            if(first_word[l] == i+1) {
                h1.append('<br>');
                l++;
            }
        }

    }
    // Or just display the text
    else {
        span.remove();
        h1.text(text);
    }

});

You can see it in action here. Unfortunately, I still don't like #8 and #13. Tips and improvements are welcome.

Upvotes: 0

Nick Giampietro
Nick Giampietro

Reputation: 228

I don't think you can do that with only CSS. Here's a Javscript snippet that you can try implementing to parse your strings. It's clunky and untested but allows you to have a preset both for the char length you want, as well as a word length. I'm sure you could clean it up and make it fit your needs.

// requires h1 field to have line-height property set
$(".box h1").each(function()
{
  // splits if over this % width
  var preset = 50;
  // indents first line to same as preset percentage
  // to attempt to make first line shorter
  var indent = String(preset) + 'px';
  // height of a standard line
  var lh = parseInt($(this).css('line-height'));
  // width of the box
  var w = $(this).parent().width() * (preset/100);

  // if the text field is one line heigh & over the preset
  if(($(this).height() <= lh) && ($(this).width() >= w))
  {
    $(this).width(w);
    $(this).css('text-indent' , indent);
  }
  // otherwise it's fine
});

Here's a link: http://codepen.io/jnickg/pen/szIpd

Upvotes: 0

Mathijs Flietstra
Mathijs Flietstra

Reputation: 12974

I have come up with a solution which utilizes a still fairly unknown JS library named MediaClass which enables the use of media queries with specific elements on the page.

I think it looks pretty good the way I've set the values but you might want to fine-tune it a little by changing widths in the JS or the CSS. Here's a jsFiddle for your tinkering pleasure.

The way it works:

JS:

MediaClass("large", "h1:media(this-min-width: 300px)");
MediaClass("small", "h1:media(this-min-width: 200px and this-max-width: 300px)");

These lines ensure that a small class is added to h1 if h1's width is between 200px and 300px and a large class if h1 is wider than 300px.

CSS:

.large:before {
    content:"\A"; 
    width: 50%;
    display: inline-block;
}
.small:before {
    content:"\A"; 
    width: 30%;
    display: inline-block;
}

This bit adds a :before pseudo-element of a width depending on the width of the h1 inside the h1, before the text, this moves the first line inside the h1 over, which changes the flow of the text.

Edit: I fixed up the post and the fiddle to better demonstrate how this solution answers the question asked.

Upvotes: 1

Tom Prats
Tom Prats

Reputation: 7921

I made a quick function that should do what you want. I commented it so you know what's going on.

$(".box h1").each(function() {
  // Check if one line
  if($(this).height() <= parseInt($(this).css('line-height'))){
    // Check if width is greater than %50 of parent
    if($(this).width() >= $(this).parent().width()/2){
      // Adjust width to put it on two lines
      $(this).width($(this).parent().width()/2)
    }
  }
});

EDIT:

To have the first line shorter than the second line, you have to do something a bit more complex. I used cut from this answer. This should be pretty close to what you want.

$(".box h1").each(function() {
  // Check if one line
  if($(this).height() <= parseInt($(this).css('line-height'))){
    // Check if width is greater than %50 of parent
    if($(this).width() >= $(this).parent().width()/2){
      // First find approximately where you want it
      place = Math.round($(this).val().length/2);         // Might need a parseFloat
      // Find nearest end of word in correct direction
      original = $(this);
      first = original.text(cut(place));

      end = first.val().length
      start = $(this).val().length - end
      second = $(this).substr(start,end)

      // Place a break tag in the middle to put it on two lines
      $(this).html(first + <br> + second)
    }
  }
});

Here's cut

function cut(n) {
    return function textCutter(i, text) {
        var short = text.substr(0, n);
        if (/^\S/.test(text.substr(n)))
            return short.replace(/\s+\S*$/, "");
        return short;
    };
}

This code uses a <br> to break up two lines (with the second longer)

EDIT2:

It's impossible to have the line lengths be different without a <br> or some other way of adding a new line. If you like it better I can change it so it uses multiple <h1> tags, which I think will automatically kick the each addition tag to a new line

Upvotes: 1

Diodeus - James MacFarlane
Diodeus - James MacFarlane

Reputation: 114417

You need JavaScript to do this. CSS cannot format text based on a complex set of rules.

Upvotes: 0

Related Questions